C#异步任务在完成之前完成

时间:2020-03-12 18:06:07

标签: c# async-await

我正在开发一个网络应用程序,该应用程序从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();
}

在前面的代码中,在上传数据之前已发送确认。令人惊讶的是,这似乎是因为我的任务使用了异步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);
    }
}

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

具有异步委托的Task构造函数的问题在于,委托的签名被解析为async void而不是async Task,因此无法等待异步操作({{ 1}}主要用于事件处理程序,而在其他大多数情况下则是有害的)。发生这种情况是因为async void构造函数无法理解异步委托,这意味着它没有接受Task参数的重载。

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

Task<Task>

如您所见,将await构造函数与异步委托一起使用会变得很尴尬,因此通常应首选任何其他方法(尤其是在编写应用程序代码时,对于库来说,恕我直言)。其他选择包括{{3}},它确实了解异步委托,或者,如果您不想立即启动任务,则传递Task<Task> uploadTaskFactory = new Task<Task>(async () => { await Task.Delay(5000, cancellationToken); // Simulates an I/O operation }); //... uploadTaskFactory.Start(); Task uploadTask = await uploadTaskFactory; // takes essentially zero time //... await uploadTask; // takes 5 seconds ,并在适当的时候调用它们。

¹准确地说:未完成的第一个Task<Task>