在 asp.net 进程中 运行 多个并行任务的正确方法是什么?
What's the correct way to run multiple parallel tasks in an asp.net process?
我想我不明白什么。我原以为 Task.Yield()
强制为任务启动一个新的 thread/context 但在 re-reading this answer 上似乎它只是强制该方法是异步的。它仍将处于相同的上下文中。
在 asp.net 进程中并行创建和 运行 多个任务而不导致死锁的正确方法是什么?
换句话说,假设我有以下方法:
async Task createFileFromLongRunningComputation(int input) {
//many levels of async code
}
并且当某个POST路由被命中时,我想同时启动上述方法3次,return立即,但是当所有三个完成了。
我想我需要在我的行动中加入这样的东西
public IHttpAction Post() {
Task.WhenAll(
createFileFromLongRunningComputation(1),
createFileFromLongRunningComputation(2),
createFileFromLongRunningComputation(3)
).ContinueWith((Task t) =>
logger.Log("Computation completed")
).ConfigureAwait(false);
return Ok();
}
createFileFromLongRunningComputation
需要填写什么?我原以为 Task.Yield
是正确的,但显然不是。
(是正确的。这个答案更多的是讨论OP提出的方法是否是一个好方法。)
您真的不需要任何特别的东西。 createFileFromLongRunningComputation
方法不需要任何特殊的东西,只要确保你在其中 await
ing 一些异步方法并且 ConfigureAwait(false)
应该 避免死锁,假设你没有做任何不寻常的事情(可能只是文件 I/O,给定方法名称)。
警告:
这是有风险的。 ASP.net 如果任务需要很长时间才能完成,那么在这种情况下,ASP.net 很可能会把地毯从你身下拉出来。
正如其中一位评论者所指出的,有更好的方法可以实现这一点。其中之一是 HostingEnvironment.QueueBackgroundWorkItem
(仅在 .NET 4.5.2 及更高版本中可用)。
如果长时间的 运行 计算需要很长时间才能完成,您最好将其完全排除在 ASP.net 之外。在那种情况下,更好的方法是使用某种消息队列,以及在 IIS/ASP.net.
之外处理这些消息的服务
将并发工作卸载到不同线程的正确方法是按照 rossipedia 的建议使用Task.Run
。
ASP.Net 中后台处理的最佳解决方案(您的 AppDomain
可以 recycled/shut 与您的所有任务一起自动关闭)在 Scott Hanselman and Stephen Cleary 的博客中(例如 HangFire)
但是,您可以 结合使用 Task.Yield
和 ConfigureAwait(false)
来达到同样的目的。
Task.Yield
所做的只是 return 一个等待程序,它确保方法的其余部分不会同步进行(通过 IsCompleted
return false
和 OnCompleted
立即执行 Action
参数)。 ConfigureAwait(false)
忽略 SynchronizationContext
,因此强制方法的其余部分在 ThreadPool
线程上执行。
如果你同时使用两者,你可以确保 async
方法 return 立即成为一个任务,它将在 ThreadPool
线程(如 Task.Run
)上执行:
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Yield().ConfigureAwait(false);
// executed on a ThreadPool thread
}
编辑:
George Mauer 指出,由于 Task.Yield
returns YieldAwaitable
你不能使用 ConfigureAwait(false)
这是 Task
class 上的方法。
您可以通过使用 Task.Delay
和非常短的超时来实现类似的效果,因此它不会是同步的,但您不会浪费太多时间:
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Delay(1).ConfigureAwait(false);
// executed on a ThreadPool thread
}
更好的选择是创建一个 YieldAwaitable
来忽略 SynchronizationContext
,就像使用 ConfigureAwait(false)
一样:
async Task CreateFileFromLongRunningComputation(int input)
{
await new NoContextYieldAwaitable();
// executed on a ThreadPool thread
}
public struct NoContextYieldAwaitable
{
public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
public struct NoContextYieldAwaiter : INotifyCompletion
{
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
var scheduler = TaskScheduler.Current;
if (scheduler == TaskScheduler.Default)
{
ThreadPool.QueueUserWorkItem(RunAction, continuation);
}
else
{
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
}
}
public void GetResult() { }
private static void RunAction(object state) { ((Action)state)(); }
}
}
这不是推荐,而是对您 Task.Yield 问题的回答。
我想我不明白什么。我原以为 Task.Yield()
强制为任务启动一个新的 thread/context 但在 re-reading this answer 上似乎它只是强制该方法是异步的。它仍将处于相同的上下文中。
在 asp.net 进程中并行创建和 运行 多个任务而不导致死锁的正确方法是什么?
换句话说,假设我有以下方法:
async Task createFileFromLongRunningComputation(int input) {
//many levels of async code
}
并且当某个POST路由被命中时,我想同时启动上述方法3次,return立即,但是当所有三个完成了。
我想我需要在我的行动中加入这样的东西
public IHttpAction Post() {
Task.WhenAll(
createFileFromLongRunningComputation(1),
createFileFromLongRunningComputation(2),
createFileFromLongRunningComputation(3)
).ContinueWith((Task t) =>
logger.Log("Computation completed")
).ConfigureAwait(false);
return Ok();
}
createFileFromLongRunningComputation
需要填写什么?我原以为 Task.Yield
是正确的,但显然不是。
(
您真的不需要任何特别的东西。 createFileFromLongRunningComputation
方法不需要任何特殊的东西,只要确保你在其中 await
ing 一些异步方法并且 ConfigureAwait(false)
应该 避免死锁,假设你没有做任何不寻常的事情(可能只是文件 I/O,给定方法名称)。
警告:
这是有风险的。 ASP.net 如果任务需要很长时间才能完成,那么在这种情况下,ASP.net 很可能会把地毯从你身下拉出来。
正如其中一位评论者所指出的,有更好的方法可以实现这一点。其中之一是 HostingEnvironment.QueueBackgroundWorkItem
(仅在 .NET 4.5.2 及更高版本中可用)。
如果长时间的 运行 计算需要很长时间才能完成,您最好将其完全排除在 ASP.net 之外。在那种情况下,更好的方法是使用某种消息队列,以及在 IIS/ASP.net.
之外处理这些消息的服务将并发工作卸载到不同线程的正确方法是按照 rossipedia 的建议使用Task.Run
。
ASP.Net 中后台处理的最佳解决方案(您的 AppDomain
可以 recycled/shut 与您的所有任务一起自动关闭)在 Scott Hanselman and Stephen Cleary 的博客中(例如 HangFire)
但是,您可以 结合使用 Task.Yield
和 ConfigureAwait(false)
来达到同样的目的。
Task.Yield
所做的只是 return 一个等待程序,它确保方法的其余部分不会同步进行(通过 IsCompleted
return false
和 OnCompleted
立即执行 Action
参数)。 ConfigureAwait(false)
忽略 SynchronizationContext
,因此强制方法的其余部分在 ThreadPool
线程上执行。
如果你同时使用两者,你可以确保 async
方法 return 立即成为一个任务,它将在 ThreadPool
线程(如 Task.Run
)上执行:
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Yield().ConfigureAwait(false);
// executed on a ThreadPool thread
}
编辑:
George Mauer 指出,由于 Task.Yield
returns YieldAwaitable
你不能使用 ConfigureAwait(false)
这是 Task
class 上的方法。
您可以通过使用 Task.Delay
和非常短的超时来实现类似的效果,因此它不会是同步的,但您不会浪费太多时间:
async Task CreateFileFromLongRunningComputation(int input)
{
await Task.Delay(1).ConfigureAwait(false);
// executed on a ThreadPool thread
}
更好的选择是创建一个 YieldAwaitable
来忽略 SynchronizationContext
,就像使用 ConfigureAwait(false)
一样:
async Task CreateFileFromLongRunningComputation(int input)
{
await new NoContextYieldAwaitable();
// executed on a ThreadPool thread
}
public struct NoContextYieldAwaitable
{
public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
public struct NoContextYieldAwaiter : INotifyCompletion
{
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
var scheduler = TaskScheduler.Current;
if (scheduler == TaskScheduler.Default)
{
ThreadPool.QueueUserWorkItem(RunAction, continuation);
}
else
{
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
}
}
public void GetResult() { }
private static void RunAction(object state) { ((Action)state)(); }
}
}
这不是推荐,而是对您 Task.Yield 问题的回答。