2

假设我创建了一个包含此类方法的库:

Task MyLibraryMethodAsync()
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    Action myWorkItem =
        () =>
        {
            // Simulate some work.
            // Actual work items depend on input params.
            Thread.Sleep(TimeSpan.FromSeconds(1));

            taskCompletionSource.SetResult(null);
        };

    // The next two lines is simplification for demonstration.
    // I do not have access to the workerThread - it is created
    // and managed for me by another lib.
    // All I can do - is to post some short work items to it.
    var workerThread = new Thread(new ThreadStart(myWorkItem));
    workerThread.Start();

    return taskCompletionSource.Task;
}

我的 lib 的任何用户都可以MyLibraryMethodAsync这样调用

await MyLibraryMethodAsync().ConfigureAwait(false);
VeryLongRunningMethod();
void VeryLongRunningMethod()
{
    Thread.Sleep(TimeSpan.FromHours(1));
}

问题来了——VeryLongRunningMethod将在taskCompletionSource.SetResult(null)调用内部执行,因此它将阻塞workerThread很长一段时间,这是不希望的行为,因为workerThread它旨在运行一小部分代码(工作项)。

如何将上下文/调度程序替换为返回的任务中的线程await x.ConfigureAwait(false)以继续在线程池上,而不是在线程池上workerThread

我找到的当前解决方案是

Task MyLibraryMethodAsync()
{
    // ...

    return taskCompletionSource.Task
        .ContinueWith(x => x.Result, TaskScheduler.Default);
}

但是,我不喜欢它,因为它会产生开销。可能存在更优雅的解决方案吗?

4

2 回答 2

4

从 .NET 4.6 开始,在TaskCreationOptionscalled中有一个选项RunContinuationsAsynchronously,它完全符合您的要求,它确保所有延续都异步运行,而不是在设置结果时同步运行。 TaskCompletionSource在其构造函数中有一个可选TaskCreationOption参数供您提供该选项。

如果您使用的是早期版本的 .NET,则需要进行效率较低的 hack,例如添加另一个延续,如您所展示的,或者在线程池线程中显式设置结果,而不是通过回调操作。

于 2016-01-07T15:18:43.540 回答
0

不确定我是否理解正确,但您可以显式创建后台任务以避免阻塞:

await MyLibraryMethodAsync().ConfigureAwait(false);
await Task.Run(() => VeryLongRunningMethod());

您甚至可以省略 ConfigureAwait :

await MyLibraryMethodAsync()
await Task.Run(() => VeryLongRunningMethod());

编辑:根据您的评论:如果您作为库的作者想要防止阻塞线程,您可以使用:

Task.Run(() => taskCompletionSource.SetResult(null));
于 2016-01-07T15:02:03.377 回答