【问题标题】:Stuck with async await thread卡在异步等待线程中
【发布时间】:2020-06-22 03:21:54
【问题描述】:

在构造函数中我想调用一种方法类型:

private async Task OnLoadPrometDanKorisnikDatum

我想在它完成时等待那个方法,我有更多这样的方法(3),我想在后台线程中调用这 3 个方法,不要等他完成,只想等待第一个方法.我想让他们并行执行。 我有方法异步任务,在视图模型的构造函数中我这样调用

OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, 
    DatumVrednost).Wait();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).Wait();

如果我没有将 .Wait() 放在最后,程序将无法运行。我在调试模式下看到它们异步运行,但花费的时间告诉我它们是子(一种方法时间 + 第二种方法时间 + ....)。 有人可以帮帮我吗,这对我来说很重要......

【问题讨论】:

  • 为什么必须在构造函数中?顺便提一句。您应该发布构造函数以向我们提供代码所在的上下文
  • 在任务上调用Wait() 在许多情况下是一个很大的禁忌;如果这里的场景有一个同步上下文(我怀疑它确实有),那么你完全有可能这样做会死锁。基本上:不要那样做(或访问.Result);你不能在这里简单地await吗?
  • @Sebastian Schumann 我尝试实施但没有成功。我实现了接口 public interface IAsyncInitialization { Task Initialization { get; } }--------------------------------public PrometPageViewModel(INavigationService navigationService, IPrometDanService servicePrometDan,IEventAggregator eventAggregator, Prism.Services .IPageDialogService pageDialogService, IAsyncInitialization asc) : base(navigationService)//,IEventAggregator ea {...Initialization = MyMethodAsync();}public Task Initialization { get;私人套装; }
  • 抱歉,这段代码不可读。请张贴类似dotnetfiddle.net 之类的内容。你有什么问题?你只告诉我你没有任何成功,但我看不出你有什么问题。

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


【解决方案1】:

回答

处理您的场景的最佳方法是使用async void

我建议首先阅读下面的Explanation 部分,以充分了解async void 的最佳实践。

public MyConstructor()
{
    ExecuteAsyncMethods();
}

async void ExecuteAsyncMethods()
{ 
    try
    {
        await OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost);
        await OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja);
    }
    catch(Exception e)
    {
        //Handle Exception
    }
}

说明

许多 C# 开发人员都被教导“永远不要使用 async void”,但这是它为数不多的用例之一。

是的 async void 可能很危险,原因如下:

  • 不能awaitasync avoid 方法
  • 可能导致竞争条件
  • 难以捕捉由async void 方法抛出的Exception
    • 例如以下try/catch 块不会捕获此处抛出的Exception
public MyConstructor()
{
    try
    {
        //Cannot await `async void`
        AsyncVoidMethodWithException();
    }
    catch(Exception e)
    {
        //Will never catch the `Exception` thrown in  `AsyncVoidMethodWithException` because `AsyncVoidMethodWithException` cannot be awaited
    }

    //code here will be executing by the time `AsyncVoidMethodWithException` throws the exception
}

async void AsyncVoidMethodWithException()
{
    await Task.Delay(2000);
    throw new Exception();
}

话虽如此,只要我们将整个 async void 的内容包装在一个 try/catch 块中,我们就能够捕获异常,如下所示:

public MyConstructor()
{
    AsyncVoidMethodWithException();
}

async void AsyncVoidMethodWithException()
{
    try
    {
        await Task.Delay(2000);
        throw new Exception();
    }
    catch(Exception e)
    {
        //Exception will be caught and successfully handled 
    }
}

SafeFireAndForget

我创建了一个库来帮助解决这个问题,它的额外好处是它避免了编写可能被未来开发人员滥用的async void 代码。

它是开源的,也可以在 NuGet 上使用:

SafeFireAndForget

SafeFireAndForget 允许我们安全地执行Task,同时不会阻塞调用线程,也无需等待它完成后再移动到下一行代码。

下面是SafeFireAndForget 的简化版本,您可以将其添加到您的项目中。

但是,我建议将其 complete source code 复制/粘贴或将其 NuGet Package 添加到您的库中以获得更强大的实现

public static async void SafeFireAndForget<TException>(this Task task, Action<TException> onException = null, bool continueOnCapturedContext = false) where TException : Exception
{
    try
    {
        await task.ConfigureAwait(continueOnCapturedContext);
    }
    catch (TException ex) when (onException != null)
    {
        onException(ex);
    }
}

使用 SafeFireAndForget

要使用SafeFireAndForget,请将其附加到您的方法调用中,如下所示:

OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget();

要处理由Task 抛出的任何Exception,请使用onException。这是一个将Exception 打印到调试控制台的示例:

OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget(ex => Debug.WriteLine(ex));
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget(ex => Debug.WriteLine(ex));

【讨论】:

  • 我认为SafeFireAndForget的简化版本会在onExceptionnull的情况下使应用程序崩溃。为了真正安全,您必须在块中包含无条件的catch
  • @TheodorZoulias 你是对的,这是我对这个库的目标之一:在Task 中显示Exception。如果在没有await 的情况下调用Task,并且在Task 中抛出Exception,则Exception 将永远消失。我见过太多的开发人员在没有await 的情况下调用Task,然后想知道为什么他们的代码会进入意外状态。
  • 在这种情况下,如果使应用程序崩溃是故意的,那么我认为名称HandleSafeFireAndForget 不适合这种方法。我建议您将 onException 参数设为强制性,并添加另一个名为 OnExceptionThrowUnhandled 的方法,该方法清楚地传达了任务中出现异常时将发生的情况!
  • Stephen Cleary 回答的相关引述:避免异步无效。它在错误处理方面具有棘手的语义;我知道有些人称其为“一发不可收拾”,但我通常使用“一发不可收拾”这个词。 citation
  • 我不得不投反对票,因为 async void 会做到这一点,但强烈建议仅在特殊情况下(事件处理程序)使用它,因为这不安全。
猜你喜欢
  • 2020-07-08
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-13
  • 2018-10-01
相关资源
最近更新 更多