如何在TaskCompletionSource.task中用另一个同步上下文/任务调度程序替换ConfigureAwai

本文关键字:上下文 同步 任务调度程序 ConfigureAwai 替换 另一个 TaskCompletionSource task | 更新日期: 2023-09-27 18:29:26

假设我创建了一个包含以下方法的库:

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)螺纹池

我目前找到的解决方案是

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

然而,我不喜欢它,因为它会产生开销。也许存在更优雅的解决方案?

如何在TaskCompletionSource.task中用另一个同步上下文/任务调度程序替换ConfigureAwai

从.NET 4.6开始,TaskCreationOptions中有一个名为RunContinuationsAsynchronously的选项,它完全可以实现您想要的功能,它确保所有延续都异步运行,而不是在设置结果时同步运行。TaskCompletionSource的构造函数中有一个可选的TaskCreationOption参数,供您提供该选项。

如果你使用的是.NET的早期版本,你需要做一个效率较低的破解,比如添加另一个延续,如图所示,或者在线程池线程中显式设置结果,而不是通过回调操作。

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

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

您甚至可以省略ConfigureAwait,然后:

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

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

Task.Run(() => taskCompletionSource.SetResult(null));