为什么要使用QueueBackgroundWorkItem异步?

时间:2016-04-29 15:53:11

标签: c# asp.net .net asynchronous async-await

async与ASP.NET QueueBackgroundWorkItem方法一起使用有什么好处?

HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
    var result = await LongRunningMethodAsync();
    // etc.
});

我的理解是异步函数用于防止长时间运行的任务阻塞主线程。但是,在这种情况下,我们不是在自己的线程中执行任务吗?与非异步版本相比有什么优势:

HostingEnvironment.QueueBackgroundWorkItem(cancellationToken =>
{
    var result = LongRunningMethod();
    // etc.
}); 

4 个答案:

答案 0 :(得分:13)

  

在ASP.NET QueueBackgroundWorkItem方法中使用async有什么好处?

它允许您的后台工作项调用异步API。

  

我的理解是异步函数用于防止长时间运行的任务阻塞主线程。但是,在这种情况下,我们还不在自己的线程中执行任务吗?

异步方法的优点是它们可以释放调用线程。没有"主线程"在ASP.NET上 - 只需要开箱即用的线程和线程池线程。在这种情况下,异步后台工作项将释放线程池线程,这可能会增加可伸缩性。

  

与非异步版本相比有什么优势

或者,您可以这样想:如果LongRunningOperationAsync是一个自然异步的操作,那么LongRunningOperation将阻止一个本来可以用于其他东西的线程。

答案 1 :(得分:4)

  

在ASP.NET QueueBackgroundWorkItem方法中使用async有什么好处?

简短回答

没有任何好处,实际上你不应该在这里使用async

答案很长

TL; DR

事实上,没有任何好处 - 在这种具体情况下,我实际上会反对它。来自MSDN

  

与普通的ThreadPool工作项不同,ASP.NET可以跟踪当前正在运行的通过此API注册的工作项数,并且ASP.NET运行时将尝试延迟AppDomain关闭,直到这些工作项完成执行。无法在ASP.NET管理的AppDomain之外调用此API。当应用程序关闭时,将提供所提供的CancellationToken信号。

     

QueueBackgroundWorkItem接受一个返回任务的回调;当回调返回时,工作项将被视为已完成。

这个解释松散地表明它是为你管理的。

根据“备注”,它可能需要Task返回回调,但文档中的签名与之相冲突:

public static void QueueBackgroundWorkItem(
    Action<CancellationToken> workItem
)

他们排除了文档的过载,这令人困惑和误导 - 但我离题了。微软的"Reference Source"来救援。这是两个重载的源代码以及scheduler的内部调用,它完成了我们所关注的所有魔法。

旁注

如果你只想要排队的Action,那就没问题,因为你可以看到他们只是在幕后使用完成的任务,但这似乎有点违反直觉。理想情况下,您实际上会有Func<CancellationToken, Task>

public static void QueueBackgroundWorkItem(
    Action<CancellationToken> workItem) {
    if (workItem == null) {
        throw new ArgumentNullException("workItem");
    }

    QueueBackgroundWorkItem(ct => { workItem(ct); return _completedTask; });
}

public static void QueueBackgroundWorkItem(
    Func<CancellationToken, Task> 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<CancellationToken, Task> 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<CancellationToken, Task>。可以找到here的来源。

正如您所看到的,SheduleWorkItem仍然在workItem变量中进行异步操作,然后实际调用ThreadPool.UnsafeQueueUserWorkItem。这会调用使用RunWorkItemImplasync的{​​{1}} - 因此您不需要处于最高级别,也不应该再次为您管理。

await

对内部here进行了更深入的阅读。

答案 2 :(得分:2)

关于标记答案的QueueBackgroundWorkItem的信息很好,但结论是错误的。

使用async with closure实际上将采用QueueBackgroundWorkItem(         Func workItem)覆盖,它将等待任务完成,并在不保留任何线程的情况下完成。

所以对此的答案是,如果要使用async / await在workItem闭包中执行任何类型的IO操作。

答案 3 :(得分:-1)

让我区分您的示例为line 1&amp;的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的优势如何?好吧,他们称之为完全不同的方法。 谁知道优势或劣势可能是什么?

当然,我们希望命名意味着它们真正实现了同样的目标,并且标记为...Async()的方法异步地执行它并且(我们假设)以某种方式更有效。

在这种情况下,line 3优于line 4的优势在于它可能会使用或阻止更少的资源来完成工作。 但谁知道没有阅读代码?

我已经看到...Async()方法明确声称在没有查看代码的情况下效率可能更高的一个示例FileStream.WriteAsync() FileStream已打开FileStream(..., FileOptions.IsASync) {1}}。在这种情况下,FileOptions.IsAsync的说明建议您获得Overlapped I/O superpowersStephen Cleary's blog表明,在没有进一步消耗线程资源的情况下,这可能会发生I / O.

这是我见过的唯一一个明确表示...Async()方法更有效率的方法。也许还有更多。