C#异步任务在完成之前完成 [英] C# async task completes before it's finished

查看:102
本文介绍了C#异步任务在完成之前完成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm developing a network application that receives data from a websocket, modifies it, and uploads it to a data service. Uploading the data takes some time and I'd like to hide that latency, uploading several messages at once. When an upload is complete I need to send an acknowledgement back through the websocket.

我创建了一个工作队列来容纳所有未完成的任务.这似乎运行良好,所以我没有将其包括在内.但是我的上传任务似乎在实际完成之前就已经完成了.这是修剪后的示例.

I've created a work queue to hold all of the outstanding tasks. This seems to be working well, so I haven't included it. But my upload task appears to be finishing before it's actually finished. Here's a trimmed down sample.

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完成.所以我将其更改为这样的内容:

In the preceding code the acknowledgement is sent before the data is uploaded. Surprisingly this seems to be because my Task uses an async lambda. For a reason I don't understand uploadTask completes when the upload is awaited. So I changed it to something like this:

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.

Now everything executes in the correct order, except when things go wrong or the operation is canceled (e.g. the server is shutdown). Now I'm dealing with AggregateExceptions and TaskCanceledExceptions.

这似乎应该比我做起来容易.我做错了吗?

This seems like it should be easier than I'm making it. Am I doing this wrong?

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

Edit Adding pseudocode that calls UploadDataAsync for context.

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);
    }
}

推荐答案

具有异步委托的Task构造函数的问题在于,委托的签名已解析为

The problem with the Task constructor having an async delegate is that the signature of the delegate is resolved to async void instead of async Task, so the asynchronous operation cannot be awaited (async void is useful mainly for event handlers, and harmful in most other cases). This happens because the Task constructor does not understand async delegates, meaning that it has no overload that accepts a Func<Task> argument.

有一种方法可以解决此问题,而无需从解决方案中删除Task构造函数,尽管专家对此并不满意.代替非通用Task,您可以使用通用

There is a way to solve this problem without removing the Task constructor from your solution, although its usage is frowned upon by the experts. Instead of a non-generic Task you can use a generic Task<TResult>, with the TResult being a Task. In other words you can use a nested Task<Task>. The job of the outer task is to create the inner task, by executing the supplied async delegate. This is a CPU-bound operation that in most cases will have a very small duration. Essentially the outer task completes as soon as the code hits the first await¹ of the async delegate, and the rest of the work (that includes the high latency I/O operation) is represented by the inner task.

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>构造函数与异步委托一起使用会变得很尴尬,因此通常应首选任何替代方法(尤其是在编写应用程序代码时,对于库来说,恕我直言).替代方法包括 Task.Run 确实可以理解异步委托,或者,如果您不想立即启动任务,则可以传递Func<Task> s并在适当的时候调用它们.

As you see using the Task<Task> constructor with an async delegate becomes rather awkward, so in general any alternative method should be preferred (especially when writing application code, for libraries it's okayish IMHO). Alternatives include the Task.Run that does understand async delegates, or, if you don't want to start the task immediately, to pass around Func<Task>s and invoke them at the right moment.

¹确切地说:未完成的等待项中的第一个await.

这篇关于C#异步任务在完成之前完成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆