如何将同步上下文/任务调度程序替换为 TaskCompletionSource.Task 中的另一个用于 ConfigureAwait(false)?
How to substitute synchronization context / task scheduler to another one inside TaskCompletionSource.Task for ConfigureAwait(false)?
假设我创建了一个包含这种方法的库:
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;
}
我的库的任何用户都可以像这样调用 MyLibraryMethodAsync
await MyLibraryMethodAsync().ConfigureAwait(false);
VeryLongRunningMethod();
void VeryLongRunningMethod()
{
Thread.Sleep(TimeSpan.FromHours(1));
}
问题来了——VeryLongRunningMethod
将在 taskCompletionSource.SetResult(null)
调用中执行,因此它将阻塞 workerThread
很长一段时间,这是不希望的行为,因为workerThread
旨在 运行 一小部分代码(工作项)。
如何将上下文/调度程序替换为返回任务中的线程池,使await x.ConfigureAwait(false)
在线程池[=36]上继续=],但不在 workerThread
?
目前我找到的解决方案是
Task MyLibraryMethodAsync()
{
// ...
return taskCompletionSource.Task
.ContinueWith(x => x.Result, TaskScheduler.Default);
}
但是,我不喜欢它,因为它会产生开销。
可能存在更优雅的解决方案?
不确定我是否理解正确,但您可以明确创建后台任务以避免阻塞:
await MyLibraryMethodAsync().ConfigureAwait(false);
await Task.Run(() => VeryLongRunningMethod());
你甚至可以省略 ConfigureAwait 然后:
await MyLibraryMethodAsync()
await Task.Run(() => VeryLongRunningMethod());
编辑:根据您的评论:如果您作为库的作者想要防止阻塞线程,您可以使用:
Task.Run(() => taskCompletionSource.SetResult(null));
从 .NET 4.6 开始,TaskCreationOptions
中有一个名为 RunContinuationsAsynchronously
的选项,它完全符合您的要求,它确保所有延续都是 运行 异步的,而不是 运行 设置结果时同步。 TaskCompletionSource
在其构造函数中有一个可选的 TaskCreationOption
参数供您提供该选项。
如果您使用的是早期版本的 .NET,则需要进行效率较低的破解,例如添加另一个延续,如您所示,或在线程池线程中显式设置结果,而不是通过回调操作。
假设我创建了一个包含这种方法的库:
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;
}
我的库的任何用户都可以像这样调用 MyLibraryMethodAsync
await MyLibraryMethodAsync().ConfigureAwait(false);
VeryLongRunningMethod();
void VeryLongRunningMethod()
{
Thread.Sleep(TimeSpan.FromHours(1));
}
问题来了——VeryLongRunningMethod
将在 taskCompletionSource.SetResult(null)
调用中执行,因此它将阻塞 workerThread
很长一段时间,这是不希望的行为,因为workerThread
旨在 运行 一小部分代码(工作项)。
如何将上下文/调度程序替换为返回任务中的线程池,使await x.ConfigureAwait(false)
在线程池[=36]上继续=],但不在 workerThread
?
目前我找到的解决方案是
Task MyLibraryMethodAsync()
{
// ...
return taskCompletionSource.Task
.ContinueWith(x => x.Result, TaskScheduler.Default);
}
但是,我不喜欢它,因为它会产生开销。 可能存在更优雅的解决方案?
不确定我是否理解正确,但您可以明确创建后台任务以避免阻塞:
await MyLibraryMethodAsync().ConfigureAwait(false);
await Task.Run(() => VeryLongRunningMethod());
你甚至可以省略 ConfigureAwait 然后:
await MyLibraryMethodAsync()
await Task.Run(() => VeryLongRunningMethod());
编辑:根据您的评论:如果您作为库的作者想要防止阻塞线程,您可以使用:
Task.Run(() => taskCompletionSource.SetResult(null));
从 .NET 4.6 开始,TaskCreationOptions
中有一个名为 RunContinuationsAsynchronously
的选项,它完全符合您的要求,它确保所有延续都是 运行 异步的,而不是 运行 设置结果时同步。 TaskCompletionSource
在其构造函数中有一个可选的 TaskCreationOption
参数供您提供该选项。
如果您使用的是早期版本的 .NET,则需要进行效率较低的破解,例如添加另一个延续,如您所示,或在线程池线程中显式设置结果,而不是通过回调操作。