【问题标题】:How to create a Task of an async Task method to catch the exceptions?如何创建异步任务方法的任务来捕获异常?
【发布时间】:2017-06-26 03:36:03
【问题描述】:

我有一个任务是在套接字上循环读取:

private async void readLoop() {
    while (!cts.IsCancelRequested()) {
        await socket.ReceiveAsync(data, ...);
        doWork(data);
    }
}

由于任务不需要在创建后立即运行,因此我使用Task 构造函数,而不是Task.Run

private Task readTask = new Task(readLoop, cts, LongRunning);
// when the task need to run:
readTask.Start()

此时它工作正常,除了当任务需要完成时,当我调用readTask.Wait()await readTask 时,ReceiveAsyncdoWork 中发生的异常未附加到readTask。这意味着即使有例外,readTask.StatusRunToCompletereadTask.Exceptionnull。符合。对于文档,这是由于异步方法返回 void,但是当我尝试这样做时:

private async Task readLoop() {...}

它不会编译:

error CS0407: 'Task WebSocketInitiator.readLoop()' has the wrong return type

那么有没有办法让任务在以后开始,同时让它跟踪任务中发生的异常?

显示问题的最小测试用例:https://dotnetfiddle.net/JIfDIn

【问题讨论】:

  • 这里的底线:您的async 方法返回void,这会阻止任何调用代码观察异常,因为没有Task 可以表示异常的方法。在这种情况下,您不应该使用async void,并且如果您遵循不这样做的推荐做法,您的问题就不会发生。为什么在更改返回类型时会得到 CS0407 我不能说;你肯定在其他地方也犯了错误,但你没有提供一个好的minimal reproducible example 可靠地重现问题,所以这个问题是什么,没有人能说。
  • @PeterDuniho 它不会编译,因为 Task() 需要 Action 作为它的第一个参数,而 action 只是 void foo(void);它不会接受任务 foo();

标签: c# asynchronous async-await task-parallel-library .net-core


【解决方案1】:

如果我理解正确,您希望推迟运行任务,直到需要它,但您希望将创建保留在构造函数中。这很好,并且有一种用于这种情况的模式。它叫做Lazy
但是,Lazy 不是异步的。 Stephen Taub has however made an async implementation called AsyncLazy.

您收到错误消息的原因是您没有为您创建的外部任务指定返回值。您还需要返回 foos 值。如果将调用包装在返回内部 Task 的 lambda 中,则可以得到它,如下所示:

var task1 = new Task<Task>(async () => await foo(),
     CancellationToken.None, TaskCreationOptions.LongRunning);
task1.Start();

但是,当您等待它时,您将等待外部任务,而不是它返回的任务。因此你需要打开它:

task1.Unwrap().Wait();

这样你就可以捕捉到异常。然而,这可能不是最好的方法,因为使用 Task.Run、async 和 await 的全部原因是为了避免这些结构。 总结:

  • 如果您需要推迟任务的调用,请使用 AsyncLazy
  • 不要调用Task构造函数
  • 使用Task.Run

【讨论】:

  • 只是吹毛求疵,但最好先解开包装再将其存储在task1
  • @KevinGosse 本来不错,但随后无法调用 .Start()
【解决方案2】:

由于任务不需要在创建后立即运行,因此我使用的是 Task 构造函数,而不是 Task.Run。

你不应该使用任务构造函数。 It has literally no rational use cases at all.

在您的情况下,听起来您不需要委托或延迟初始化或类似的东西;由于您的成员和方法是私有的,因此在任务执行时分配任务成员同样有意义:

private async Task readLoop();

// when the task needs to run:
readTask = Task.Run(() => readLoop());

但是,如果您确实需要表示一些代码以在将来而不是现在运行,那么您需要一个委托。在这种情况下,asynchronous delegate:Func&lt;Task&gt;:

Func<Task> func = () => Task.Run(() => readLoop());
...
Task task = func();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-27
    • 2022-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多