配置TaskCompletionSource的任务的延续行为

时间:2012-10-02 15:08:09

标签: c# .net clr task-parallel-library async-await

请考虑以下代码:

public Task SomeAsyncMethod()
{
    var tcs = new TaskCompletionSource();
    ... do something, NOT setting the TaskCompletionSource...

    return tcs.Task
}

public void Callsite1()
{
    await SomeAsyncMethod();
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

public void Callsite2()
{
    SomeAsyncMethod().ContinueWith((task) =>
    {
        Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    });
}

在某个时间点,在SomeAsyncMethod中创建的TaskCompletionSource是在ThreadPool线程上设置的:

Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
tcs.SetResult();

当等待来自TaskCompletionSource的Task(如在Callsite1中)时,继续在调用SetResult的Thread上同步执行。调用ContinueWith时(如在Callsite2中),继续在另一个线程上异步执行。

中调用configure await没有帮助
public void Callsite1()
{
    await SomeAsyncMethod().ConfigureAwait(true or false);
}

这甚至不是这个问题的重点。作为SomeAsyncMethod的实现者,我不想通过调用SetResult来调用一些可能未知的代码。我希望延迟始终是异步安排的。并且我不能依赖调用者来正确配置await(如果这甚至可以工作)。

如何配置TaskCompletionSource,使其任务在等待时不会同步执行其继续?

3 个答案:

答案 0 :(得分:11)

无法阻止同步任务延续。通常情况下,这不是问题。

但是,在某些情况下您需要这样做,例如,如果您在持有锁定的同时完成任务。在这些情况下,您可以Task.Run任务完成,因此:

// Set the result on a threadpool thread, so any synchronous continuations
//  will execute in the background.
Task.Run(() => tcs.TrySetResult(result));

// Wait for the TCS task to complete; note that the continuations
//  may not be complete.
tcs.Task.Wait();

这是一项先进的技术。这是guideline "Don't block on async code (async all the way down)" as expounded on my blog的例外。

这是我AsyncEx library的一部分,作为扩展方法:

public static void TrySetResultWithBackgroundContinuations<TResult>(
    this TaskCompletionSource<TResult> @this, TResult result);

这项技术是first published by Stephen Toub on his blog

答案 1 :(得分:2)

  

如何配置TaskCompletionSource,使其任务在等待时不会同步执行其继续?

不可能。您公开展示Task,一旦您这样做,任何人都可以自由附加同步延续(他们只需要使用ContinueWith的另一个重载,他们不需要 使用async / await)。

答案 2 :(得分:1)

自.NET 4.6起,TaskCreationOption.RunContinuationsAsynchronously仅针对此类或案例添加:

  

[强制]延续添加到当前任务中以异步方式执行。

因此,您可以使用TaskCompletionSource创建TaskCreationOptions.RunContinuationsAsynchronously,并确保调用其.SetResult()不会同步执行随机代码。

var tcs = new TaskCompletionSource<bool>(
    TaskCreationOptions.RunContinuationsAsynchronously);

// some time later, somewhere else
tcs.SetResult(true);
DoMoreStuffWithoutWorryingThatSetResultJustRanRandomCode();

Stephen Toub's blog post "New Task APIs in .NET 4.6"中提供了更多详细信息。

  

当使用此选项创建的任务完成时,它甚至不会尝试同步调用continuation ...它只是异步调用所有的continuation,就像没有人要求同步执行一样,如果可能的话

如果您需要类似.ContinueWith()之类的行为,还有TaskContinuationOptions.RunContinuationsAsynchronously

  

[TaskContinuationOptions.RunContinuationsAsynchronously指定]延迟任务应该异步运行。此选项优先于TaskContinuationOptions.ExecuteSynchronously。