以非阻塞方式调用 TaskCompletionSource.SetResult [英] Calling TaskCompletionSource.SetResult in a non blocking manner

查看:27
本文介绍了以非阻塞方式调用 TaskCompletionSource.SetResult的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现 TaskCompletionSource.SetResult(); 在返回之前调用等待任务的代码.就我而言,这会导致死锁.

I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.

这是在普通Thread

void ReceiverRun()
    while (true)
    {
        var msg = ReadNextMessage();
        TaskCompletionSource<Response> task = requests[msg.RequestID];

        if(msg.Error == null)
            task.SetResult(msg);
        else
            task.SetException(new Exception(msg.Error));
    }
}

代码的异步"部分看起来像这样.

The "async" part of the code looks something like this.

await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();

Wait 实际上嵌套在非异步调用中.

The Wait is actually nested inside non-async calls.

SendAwaitResponse(简化)

The SendAwaitResponse(simplified)

public static Task<Response> SendAwaitResponse(string msg)
{
    var t = new TaskCompletionSource<Response>();
    requests.Add(GetID(msg), t);
    stream.Write(msg);
    return t.Task;
}

我的假设是第二个 SendAwaitResponse 将在 ThreadPool 线程中执行,但它会在为 ReceiverRun 创建的线程中继续执行.

My assumption was that the second SendAwaitResponse would execute in a ThreadPool thread but it continues in the thread created for ReceiverRun.

有没有办法在不继续等待的代码的情况下设置任务的结果?

Is there anyway to set the result of a task without continuing its awaited code?

该应用程序是一个控制台应用程序.

推荐答案

我发现 TaskCompletionSource.SetResult();在返回之前调用等待任务的代码.就我而言,这会导致死锁.

I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.

是的,我有一篇博文 对此进行记录(AFAIK 未在 MSDN 上记录).发生僵局的原因有两个:

Yes, I have a blog post documenting this (AFAIK it's not documented on MSDN). The deadlock happens because of two things:

  1. async 和阻塞代码的混合(即,async 方法正在调用 Wait).
  2. 使用 TaskContinuationOptions.ExecuteSynchronously 安排任务延续.
  1. There's a mixture of async and blocking code (i.e., an async method is calling Wait).
  2. Task continuations are scheduled using TaskContinuationOptions.ExecuteSynchronously.

我建议从最简单的解决方案开始:删除第一件事 (1).即,不要混合 asyncWait 调用:

I recommend starting with the simplest possible solution: removing the first thing (1). I.e., don't mix async and Wait calls:

await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();

相反,始终使用 await:

await SendAwaitResponse("first message");
await SendAwaitResponse("second message");

如果需要,您可以等待在调用堆栈更上一层的替代点(不是async方法中).

If you need to, you can Wait at an alternative point further up the call stack (not in an async method).

这是我最推荐的解决方案.但是,如果您想尝试删除第二件事 (2),您可以使用一些技巧:将 SetResult 包装在 Task.Run 中以强制它进入一个单独的线程(我的 AsyncEx library*WithBackgroundContinuations 扩展方法),或者给你的线程一个实际的上下文(比如我的 AsyncContext 类型) 并指定 ConfigureAwait(false),这将 导致继续忽略ExecuteSynchronously 标志.

That's my most-recommended solution. However, if you want to try removing the second thing (2), you can do a couple of tricks: either wrap the SetResult in a Task.Run to force it onto a separate thread (my AsyncEx library has *WithBackgroundContinuations extension methods that do exactly this), or give your thread an actual context (such as my AsyncContext type) and specify ConfigureAwait(false), which will cause the continuation to ignore the ExecuteSynchronously flag.

但是这些解决方案比仅仅分离async和阻塞代码要复杂得多.

But those solutions are much more complex than just separating the async and blocking code.

附带说明,请查看 TPL 数据流;听起来你可能会觉得它很有用.

As a side note, take a look at TPL Dataflow; it sounds like you may find it useful.

这篇关于以非阻塞方式调用 TaskCompletionSource.SetResult的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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