【问题标题】:Xamarin.Forms - BeginInvokeOnMainThread for an async ActionXamarin.Forms - BeginInvokeOnMainThread 用于异步操作
【发布时间】:2016-09-24 06:13:05
【问题描述】:

我熟悉使用 Device.BeginInvokeOnMainThread 在 UI 线程上更新 UI 元素的规则,但是我有一个操作需要在实际上是一个任务的 UI 线程上运行。

例如,XLabs.Forms.Mvvm 上的 Push/PopAsync 方法在 iOS 上的行为似乎不正确,除非在 UI 线程上调用它们。 Acr.UserDialogs 库中还有另一个示例,用于显示 toast 等。

我知道,使 Action 异步基本上是创建一个异步 void lambda,并且在发生异常的情况下冒着创建死锁的风险,显然我不希望这种情况发生。

是否有人有解决方法来在 UI 线程上执行不涉及将操作标记为异步的异步操作?

【问题讨论】:

    标签: c# xamarin async-await xamarin.forms


    【解决方案1】:

    Xamarin.Forms 4.2 开始,现在有一个辅助方法可以在主线程上运行任务并等待它们。

    await Device.InvokeOnMainThreadAsync(SomeAsyncMethod);
    

    存在几个应该涵盖大多数场景的重载:

    System.Threading.Tasks.Task InvokeOnMainThreadAsync (System.Action action);
    System.Threading.Tasks.Task InvokeOnMainThreadAsync (System.Func<System.Threading.Tasks.Task> funcTask);
    System.Threading.Tasks.Task<T> InvokeOnMainThreadAsync<T> (System.Func<System.Threading.Tasks.Task<T>> funcTask);
    System.Threading.Tasks.Task<T> InvokeOnMainThreadAsync<T> (System.Func<T> func);
    

    相关公关:https://github.com/xamarin/Xamarin.Forms/pull/5028

    注意:PR 有一些在 v4.2 中修复的错误,所以不要在 v4.1 中使用它。

    【讨论】:

      【解决方案2】:

      只要确保在 Action 中处理异常就可以了。当您处理异常时,就会出现您描述的问题。下面是一个从主线程运行异步方法的非常简单的示例。

      private void Test()
      {
          Device.BeginInvokeOnMainThread(SomeMethod);
      }
      
      private async void SomeMethod()
      {
          try
          {
              await SomeAsyncMethod();
          }
          catch (Exception e) // handle whatever exceptions you expect
          {
              //Handle exceptions
          }
      }
      
      private async Task SomeAsyncMethod()
      {
          await Navigation.PushModalAsync(new ContentPage());
      }
      

      【讨论】:

        【解决方案3】:

        如果有人想等待必须在主线程上执行的动作结束,则将其放在这里

        public static class DeviceHelper
        {
            public static Task RunOnMainThreadAsync(Action action)
            {
                var tcs = new TaskCompletionSource<object>();
                Device.BeginInvokeOnMainThread(
                    () =>
                    {
                        try
                        {
                            action();
                            tcs.SetResult(null);
                        }
                        catch (Exception e)
                        {
                            tcs.SetException(e);
                        }
                    });
        
                return tcs.Task;
            }
        
            public static Task RunOnMainThreadAsync(Task action)
            {
                var tcs = new TaskCompletionSource<object>();
                Device.BeginInvokeOnMainThread(
                    async () =>
                    {
                        try
                        {
                            await action;
                            tcs.SetResult(null);
                        }
                        catch (Exception e)
                        {
                            tcs.SetException(e);
                        }
                    });
        
                return tcs.Task;
            }
        }
        

        【讨论】:

        • 我理解你第一个函数的目的,但是第二个呢?任务不是在主线程中启动,而是在其中等待
        • 如果 action 是 async void 它将匹配函数一并且该调用的结束将不可等待 - 第二个启用您想要等待执行您的方法(任务)的场景重新通过。
        • 您想要在主线程中等待函数的场景...?你为什么要那个?
        • 有时你要运行的代码必须在ui线程上执行。究竟是什么让你如此惊讶?
        • 好吧,即使你在主线程上等待,也并不意味着在该线程上继续执行,AFAIU(参见 ConfigueAwait(bool))
        【解决方案4】:

        评估 Maui 框架和相关的WeatherTwentyOne 演示。这些对话框在 Android 29 上引发了与身份验证修改相关的异常。这篇文章的提示Why does my app crash after alert dialog buttons are clicked

        无论如何,我找不到上述异步助手。所以我修改了@Dbl's 帖子以包含等待的通用结果(或不包含)

        using System;
        using System.Threading.Tasks;
        using Microsoft.Maui.Controls;
        
        // ReSharper disable AsyncVoidLambda
        
        namespace WeatherTwentyOne.Utils
        {
            public static class DeviceHelper
            {
                // https://stackoverflow.com/a/47941859/241296
        
                public static Task<T> RunOnMainThreadAsync<T>(Func<Task<T>> op)
                {
                    var tcs = new TaskCompletionSource<T>();
                    Device.BeginInvokeOnMainThread(async
                        () => {
                        try
                        {
                            var t = await op();
                            tcs.SetResult(t);
                        }
                        catch (Exception e)
                        {
                            tcs.SetException(e);
                        }
                    });
        
                    return tcs.Task;
                }
        
                public static Task RunOnMainThreadAsync(Func<Task> op)
                {
                    var tcs = new TaskCompletionSource();
                    Device.BeginInvokeOnMainThread(async
                        () => {
                        try
                        {
                            await op();
                            tcs.SetResult();
                        }
                        catch (Exception e)
                        {
                            tcs.SetException(e);
                        }
                    });
        
                    return tcs.Task;
                }
            }
        }
        

        示例用法:

        var password = await Utils.DeviceHelper.RunOnMainThreadAsync<string>(async () => await DisplayPromptAsync("Login", "Enter password"));
        

        【讨论】:

          猜你喜欢
          • 2019-09-24
          • 1970-01-01
          • 2023-03-16
          • 1970-01-01
          • 2021-06-30
          • 1970-01-01
          • 2016-02-27
          • 2023-03-31
          • 2017-06-29
          相关资源
          最近更新 更多