为什么在QueueBackgroundWorkItem中使用异步?
使用ASP.NET QueueBackgroundWorkItem
方法的async
有什么好处?
HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => { var result = await LongRunningMethodAsync(); // etc. });
我的理解是异步函数用于防止长时间运行的任务阻塞主线程。 但是,在这种情况下,我们不是要在自己的线程中执行任务吗? 与非异步版本相比有什么优势:
HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => { var result = LongRunningMethod(); // etc. });
使用ASP.NET QueueBackgroundWorkItem方法的异步有什么好处?
它允许您的后台工作项调用异步API。
我的理解是异步函数用于防止长时间运行的任务阻塞主线程。 但是,在这种情况下,我们不是要在自己的线程中执行任务吗?
异步方法的优点是它们释放了调用线程。 ASP.NET上没有“主线程” – 只需要开箱即用的线程和线程池线程。 在这种情况下,异步后台工作项将释放线程池线程,这可能会增加可伸缩性。
与非异步版本相比有什么优势
或者,您可以这样想:如果LongRunningOperationAsync
是一个自然异步操作,那么LongRunningOperation
将阻止一个线程,否则该线程可以用于其他东西。
使用ASP.NET QueueBackgroundWorkItem方法的异步有什么好处?
简短的回答
没有任何好处,实际上你不应该在这里使用async
!
答案很长
TL; DR
事实上没有任何好处 – 在这种特殊情况下,我实际上会反对它。 来自MSDN :
与普通的ThreadPool工作项不同,ASP.NET可以跟踪通过此API注册的工作项当前正在运行的数量,并且ASP.NET运行时将尝试延迟AppDomain关闭,直到这些工作项完成执行。 无法在ASP.NET管理的AppDomain之外调用此API。 提交的CancellationToken将在应用程序关闭时发出信号。
QueueBackgroundWorkItem接受一个返回任务的回调; 当回调返回时,工作项将被视为已完成。
这个解释松散地表明它是为你管理的。
根据“备注”,它可能需要一个Task
返回回调,但是文档中的签名与以下内容冲突:
public static void QueueBackgroundWorkItem( Action workItem )
他们排除了文档的过载,这令人困惑和误导 – 但我离题了。 微软的“参考资料”来救援。 这是两个重载的源代码以及对scheduler
的内部调用,它完成了我们所关注的所有魔法。
边注
如果你只想要排队一个模糊的Action
,那就没问题,因为你可以看到他们只是在幕后使用完成的任务,但这似乎有点违反直觉。 理想情况下,您实际上会有一个Func
。
public static void QueueBackgroundWorkItem( Action workItem) { if (workItem == null) { throw new ArgumentNullException("workItem"); } QueueBackgroundWorkItem(ct => { workItem(ct); return _completedTask; }); } public static void QueueBackgroundWorkItem( Func workItem) { if (workItem == null) { throw new ArgumentNullException("workItem"); } if (_theHostingEnvironment == null) { throw new InvalidOperationException(); // can only be called within an ASP.NET AppDomain } _theHostingEnvironment.QueueBackgroundWorkItemInternal(workItem); } private void QueueBackgroundWorkItemInternal( Func workItem) { Debug.Assert(workItem != null); BackgroundWorkScheduler scheduler = Volatile.Read(ref _backgroundWorkScheduler); // If the scheduler doesn't exist, lazily create it, but only allow one instance to ever be published to the backing field if (scheduler == null) { BackgroundWorkScheduler newlyCreatedScheduler = new BackgroundWorkScheduler(UnregisterObject, Misc.WriteUnhandledExceptionToEventLog); scheduler = Interlocked.CompareExchange(ref _backgroundWorkScheduler, newlyCreatedScheduler, null) ?? newlyCreatedScheduler; if (scheduler == newlyCreatedScheduler) { RegisterObject(scheduler); // Only call RegisterObject if we just created the "winning" one } } scheduler.ScheduleWorkItem(workItem); }
最终你最终得到scheduler.ScheduleWorkItem(workItem);
其中workItem
表示异步操作Func
。 可在此处找到此源。
正如您所看到的, SheduleWorkItem
仍然在workItem
变量中进行异步操作,然后它实际上调用了ThreadPool.UnsafeQueueUserWorkItem
。 这会调用RunWorkItemImpl
,它使用async
和await
– 因此您不需要处于最高级别,并且您不应该再为它管理它。
public void ScheduleWorkItem(Func workItem) { Debug.Assert(workItem != null); if (_cancellationTokenHelper.IsCancellationRequested) { return; // we're not going to run this work item } // Unsafe* since we want to get rid of Principal and other constructs specific to the current ExecutionContext ThreadPool.UnsafeQueueUserWorkItem(state => { lock (this) { if (_cancellationTokenHelper.IsCancellationRequested) { return; // we're not going to run this work item } else { _numExecutingWorkItems++; } } RunWorkItemImpl((Func)state); }, workItem); } // we can use 'async void' here since we're guaranteed to be off the AspNetSynchronizationContext private async void RunWorkItemImpl(Func workItem) { Task returnedTask = null; try { returnedTask = workItem(_cancellationTokenHelper.Token); await returnedTask.ConfigureAwait(continueOnCapturedContext: false); } catch (Exception ex) { // ---- exceptions caused by the returned task being canceled if (returnedTask != null && returnedTask.IsCanceled) { return; } // ---- exceptions caused by CancellationToken.ThrowIfCancellationRequested() OperationCanceledException operationCanceledException = ex as OperationCanceledException; if (operationCanceledException != null && operationCanceledException.CancellationToken == _cancellationTokenHelper.Token) { return; } _logCallback(AppDomain.CurrentDomain, ex); // method shouldn't throw } finally { WorkItemComplete(); } }
这里有更深入的内部读物。
关于标记答案的QueueBackgroundWorkItem的信息很好,但结论是错误的。
使用async with closure实际上会占用QueueBackgroundWorkItem(Func workItem)覆盖,它将等待任务完成,并在不保留任何线程的情况下完成。
所以答案是如果你想使用async / await在workItem闭包中执行任何类型的IO操作。
让我区分您的示例为line 1
line 4
和line 4
个代码段。
(我认为我们中的一些人将你的问题看作是关于第1行与第3行;其他关于第3行与第4行的关系。我认为你的实际问题1对4提出了两个问题)。
(– where HE.QueueBWI is actually HostingEnvironment.QueueBackgroundWorkItem() –) HE.QueueBWI(async ct => { var result=await LongRunningMethodAsync(); /* etc */ }); HE.QueueBWI( ct => { var result= LongRunningMethodAsync().ContinueWith(t => {/*etc*/});}); HE.QueueBWI( ct => LongRunningMethodAsync() ); HE.QueueBWI( ct => LongRunningMethod() );
第line 1
async关键字的好处是它允许您使用简单的await
语法,这种语法比line 2
更容易。 但是如果你没有使用结果,那么你不需要await
它,并且可以使用更具可读性的line 3
。
line 3
line 4
line 3
的优势怎么样? 好吧,他们称之为完全不同的方法。 谁知道优势或劣势可能是什么?
当然,我们期望命名意味着它们实际上实现了同样的目标,并且标记的方法是...Async()
异步地执行它并且(我们假设 )以某种方式更有效。
在这种情况下,第line 3
line 4
的优势在于它可能会使用或阻止更少的资源来完成工作。 但谁知道没有阅读代码?
我已经看到了一个例子,当FileStream
用FileStream(..., FileOptions.IsASync)
打开时, ...Async()
方法被明确声称可能更高效而不查看代码是FileStream.WriteAsync()
FileStream(..., FileOptions.IsASync)
。 在这种情况下, FileOptions.IsAsync的描述表明您将获得重叠的I / O超级大国 。 Stephen Cleary的博客建议,这可能会发生I / O,而不会进一步消耗线程资源。
这是我见过的唯一一个例子...Async()
方法被明确地说做了更高效的事情。 也许还有更多。