【问题标题】:C# async task completes before it's finishedC# 异步任务在完成之前完成
【发布时间】:2022-03-28 19:32:27
【问题描述】:

我正在开发一个网络应用程序,该应用程序从 websocket 接收数据,对其进行修改,并将其上传到数据服务。上传数据需要一些时间,我想隐藏这种延迟,一次上传多条消息。上传完成后,我需要通过 websocket 发回确认。

我创建了一个工作队列来保存所有未完成的任务。这似乎运作良好,所以我没有包括它。但是我的上传任务似乎在它实际完成之前就完成了。这是一个精简的示例。

private async Task UploadDataAsync(string data, CancellationToken cancellationToken)
{
    Task uploadTask = new Task(async () =>
    {
        // Simulates uploading data
        await Task.Delay(5000, cancellationToken);
    });

    _ = uploadTask.ContinueWith(async (t1) =>
    {
        // Clean up the task
        await RemoveTask(t1);

        if (t1.IsCompletedSuccessfully)
            await SendAck(this, data, cancellationToken);
        else if (t1.IsFaulted)
            logger.LogError(t1.Exception, $"An error occurred while uploading {data}");
        else if (t1.IsCanceled)
            logger.LogWarning($"An upload task {data} was canceled");

    }, TaskScheduler.Default);

    await AddTask(uploadTask);
    uploadTask.Start();
}

在前面的代码中,确认是在数据上传之前发送的。令人惊讶的是,这似乎是因为我的 Task 使用了异步 lambda。出于某种原因,我不明白 uploadTask 在等待上传时完成。所以我把它改成了这样:

private async Task UploadDataAsync(string data, CancellationToken cancellationToken)
{
    Task uploadTask = new Task(() =>
    {
        // Simulates uploading data
        Task.Delay(5000, cancellationToken).Wait();
    });

    _ = uploadTask.ContinueWith((t1) =>
    {
        // Clean up the task
        RemoveTask(t1).Wait();

        if (t1.IsCompletedSuccessfully)
            SendAck(this, data, cancellationToken).Wait();
        else if (t1.IsFaulted)
            logger.LogError(t1.Exception, $"An error occurred while uploading {data}");
        else if (t1.IsCanceled)
            logger.LogWarning($"An upload task {data} was canceled");

    }, TaskScheduler.Default);

    await AddTask(uploadTask);
    uploadTask.Start();
}

现在一切都以正确的顺序执行,除非出现问题或操作被取消(例如服务器关闭)。现在我正在处理 AggregateExceptions 和 TaskCanceledExceptions。

这看起来应该比我做的更容易。我做错了吗?

编辑添加调用UploadDataAsync作为上下文的伪代码。

protected override async Task DoConnection(CancellationToken cancellationToken)
{
    while (_websocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
    {
        // Simulates getting websocket data
        string result = await _websocket.ReceiveAsync();

        // This should only asynchronously wait for the upload task to get
        // created. It should not wait for the upload to complete.
        await OnDataReceived(result, cancellationToken);
    }
}

【问题讨论】:

  • 你在哪里等待uploadTask?你在AddTask做什么?
  • 在第一个代码的末尾添加 uploadTask.Wait()。
  • @HirasawaYui 一般来说,在 C# 中混合使用 await.Wait() 是个坏主意 - 您应该使用其中一个。否则,在大多数情况下它会死锁(请参阅链接文章)。
  • "实例化一个Task对象并启动一个任务的最常用方法是调用静态Task.Run(Action)TaskFactory.StartNew(Action)方法,而不是调用这个构造函数。用于需要任务的创建和启动分离的高级场景。” Link

标签: c# async-await


【解决方案1】:

Task构造函数有一个异步委托的问题是委托的签名被解析为async void而不是async Task,所以不能等待异步操作(async void主要用于事件处理程序,并且在大多数其他情况下有害)。发生这种情况是因为 Task 构造函数不理解异步委托,这意味着它没有接受 Func<Task> 参数的重载。

有一种方法可以在不从您的解决方案中删除 Task 构造函数的情况下解决此问题,尽管专家不赞成使用它。您可以使用通用Task<TResult> 而不是非通用TaskTResultTask。换句话说,您可以使用嵌套的Task<Task>。外部任务的工作是通过执行提供的异步委托来创建内部任务。这是一个受 CPU 限制的操作,在大多数情况下,持续时间非常短。基本上,只要代码到达异步委托的第一个await¹,外部任务就完成了,其余工作(包括高延迟 I/O 操作)由内部任务表示。

Task<Task> uploadTaskFactory = new Task<Task>(async () =>
{
    await Task.Delay(5000, cancellationToken); // Simulates an I/O operation
});

//...
uploadTaskFactory.Start(TaskScheduler.Default);
Task uploadTask = await uploadTaskFactory; // takes essentially zero time

//...
await uploadTask; // takes 5 seconds

正如您所见,使用带有异步委托的 Task&lt;Task&gt; 构造函数变得相当笨拙,因此通常应该首选任何替代方法(尤其是在编写应用程序代码时,恕我直言,对于库来说这是可以的)。替代方案包括了解异步委托的 Task.Run,或者,如果您不想立即启动任务,则传递 Func&lt;Task&gt;s 并在适当的时候调用它们。

¹ 准确地说:未完成的可等待对象的第一个await

【讨论】:

  • 我修改了整个答案,因为原来的答案不正确,代码无法编译。
【解决方案2】:

现在,您拨打uploadTask.Start() 并继续,无需等待它完成。 (即使AddTask 发生在您调用uploadTask.Start() 之前)。您应该等待uploadTask,而不是立即启动它并继续前进。

【讨论】:

  • 关键是这将并行执行。调用者应该能够继续,因此等待会阻止调用者继续,直到数据上传。
猜你喜欢
  • 1970-01-01
  • 2016-07-16
  • 2017-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多