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

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

问题描述

我发现 TaskCompletionSource.SetResult(); 调用code返回前等待的任务。在我的情况下导致死锁。

这是在一个普通的发开始的简化版本

 无效ReceiverRun()
    而(真)
    {
        VAR味精= ReadNextMessage();
        TaskCompletionSource<应变及GT;任务请求= [msg.RequestID]        如果(msg.Error == NULL)
            task.SetResult(MSG);
        其他
            task.SetException(新的异常(msg.Error));
    }
}

在code的异步的部分看起来像这样。

 等待SendAwaitResponse(第一条消息);
SendAwaitResponse(第二个信息)等待()。

等待实际上是嵌套在非异步调用。

该SendAwaitResponse(简体)

 公共静态任务<应变及GT; SendAwaitResponse(string信息)
{
    变种T =新TaskCompletionSource<应变及GT;();
    requests.Add(的getId(MSG),T);
    stream.Write(MSG);
    返回t.Task;
}

我的假设是,第二SendAwaitResponse将在一个线程池的线程中执行,但它仍然在ReceiverRun创建的线程。

反正有没有设定一个任务的结果,而不继续等待code?

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


解决方案

  

我发现TaskCompletionSource.SetResult();调用code返回前等待的任务。在我的情况下导致死锁。


是的,我有一个博客文章文档化这(据我所知它不记录在MSDN上)。死锁是因为两件事情:


  1. 的混合物异步和阻断code(即一个异步方法被调用等待)。

  2. 任务延续使用的是定 TaskContinuationOptions.ExecuteSynchronously

我建议先从最简单可行的解决方案:去除第一件事(1)。即,不混异步等待调用:

 等待SendAwaitResponse(第一条消息);
SendAwaitResponse(第二个信息)等待()。

相反,使用等待一致:

 等待SendAwaitResponse(第一条消息);
等待SendAwaitResponse(第二个信息);

如果你需要,可以在一个替代点等待进一步调用堆栈(的未在异步法)。

这是我最推荐的解决方案。但是,如果您想尝试删除第二件事(2),你可以做一些技巧的,既可以在任务包住的setResult 。运行来迫使它到一个单独的线程(我的<一href=\"http://nitoasyncex.$c$cplex.com/wikipage?title=TaskCompletionSourceExtensions&referringTitle=Documentation\">AsyncEx库有 * WithBackgroundContinuations 扩展方法是做的正是这一点),或给你的线程实际环境(如我的<一个href=\"http://nitoasyncex.$c$cplex.com/wikipage?title=AsyncContext&referringTitle=Documentation\"><$c$c>AsyncContext键入),并指定 ConfigureAwait(假),这将的cause延续忽略 ExecuteSynchronously 标志

但这些解决方案不仅仅是分离复杂得多异步和阻断code。

作为一个方面说明,看看 TPL数据流;这听起来像你可能会发现它很有用。

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

This is a simplified version that is started in an ordinary 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();

The Wait is actually nested inside non-async calls.

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

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?

The application is a console application.

解决方案

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

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

  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.

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

Instead, use await consistently:

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

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

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.

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

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

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

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