为什么在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 ,它使用asyncawait – 因此您不需要处于最高级别,并且您不应该再为它管理它。

 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 4line 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的优势在于它可能会使用或阻止更少的资源来完成工作。 但谁知道没有阅读代码?

我已经看到了一个例子,当FileStreamFileStream(..., FileOptions.IsASync)打开时, ...Async()方法被明确声称可能更高效而不查看代码是FileStream.WriteAsync() FileStream(..., FileOptions.IsASync) 。 在这种情况下, FileOptions.IsAsync的描述表明您将获得重叠的I / O超级大国 。 Stephen Cleary的博客建议,这可能会发生I / O,而不会进一步消耗线程资源。

这是我见过的唯一一个例子...Async()方法被明确地说做了更高效的事情。 也许还有更多。