为什么要使用异步与QueueBackgroundWorkItem? [英] Why use async with QueueBackgroundWorkItem?

查看:392
本文介绍了为什么要使用异步与QueueBackgroundWorkItem?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么是使用的好处异步与ASP.NET QueueBackgroundWorkItem 方法?

  HostingEnvironment.QueueBackgroundWorkItem(异步的CancellationToken =>
{
    VAR的结果=等待LongRunningMethodAsync();
    //等等。
});

我的理解是,异步函数用于prevent长时间运行的任务阻塞主线程。然而,在这种情况下,不我们执行任务在其自己的线程呢?什么是在非异步版本的优点是:

  HostingEnvironment.QueueBackgroundWorkItem(的CancellationToken =>
{
    VAR的结果= LongRunningMethod();
    //等等。
});


解决方案

  

什么是使用异步与ASP.NET QueueBackgroundWorkItem方法的好处?


简短的回答

有没有好处,其实你不应该使用异步在这里!

龙答案

TL; DR

有没有好处,事实上 - 在这个具体的情况我真的建议反对。从 MSDN


  

这在ASP.NET一个正常的线程池工作项目的不同可以跟踪通过这个API注册的许多工作项目如何正在运行,而ASP.NET运行时会尝试,直到这些工作项目完成执行拖延的AppDomain关闭。此API不能在ASP.NET管理的AppDomain的外部调用。所提供的CancellationToken会在应用程序关闭来通知。


  
  

QueueBackgroundWorkItem接受了一个任务,返回回调;该工作项目将被视为完成后回调的回报。


这说明松散表明它是为你管理。

据备注它理应需要一个工作返回回调,不过在签名与文档冲突:

 公共静态无效QueueBackgroundWorkItem(
        动作<&的CancellationToken GT;工作项目
    )

他们排除的文件,这是混淆和误导过载 - 但我离题。微软的参考源救援。这是两个重载源$ C ​​$ C以及内部调用到调度这做所有我们关注的神奇。

旁注

如果你只有一个模糊的动作要排队,这很好,你可以看到他们简单地用一个完成任务,为您在幕后,但似乎有点反直觉。理想情况下,你会实际上有一个 Func键<的CancellationToken,工作方式>

 公共静态无效QueueBackgroundWorkItem(
        动作<&的CancellationToken GT;工作项目){
        如果(工作项目== NULL){
            抛出新的ArgumentNullException(工作项目);
        }        QueueBackgroundWorkItem(CT = GT; {工作项目(CT);返回_completedTask;});
    }    公共静态无效QueueBackgroundWorkItem(
        FUNC<的CancellationToken,任务>工作项目){
        如果(工作项目== NULL){
            抛出新的ArgumentNullException(工作项目);
        }
        如果(_theHostingEnvironment == NULL){
            抛出新的InvalidOperationException异常(); //只能在一个ASP.NET的AppDomain中调用
        }        _theHostingEnvironment.QueueBackgroundWorkItemInternal(工作项目);
    }    私人无效QueueBackgroundWorkItemInternal(
        FUNC<的CancellationToken,任务>工作项目){
        Debug.Assert的(工作项目!= NULL);        BackgroundWorkScheduler调度= Volatile.Read(REF _backgroundWorkScheduler);        //如果调度不存在,懒洋洋地创建,但只允许一个实例永远被发布到支持字段
        如果(调度== NULL){
            BackgroundWorkScheduler newlyCreatedScheduler =新BackgroundWorkScheduler(UnregisterObject,Misc.WriteUnhandledExceptionToEventLog);
            调度= Interlocked.CompareExchange(REF _backgroundWorkScheduler,newlyCreatedScheduler,NULL)? newlyCreatedScheduler;
            如果(调度== newlyCreatedScheduler){
                RegisterObject(调度); //只有调用RegisterObject如果我们刚创建的​​中奖一
            }
        }        scheduler.ScheduleWorkItem(工作项目);
    }

最后你结束了 scheduler.ScheduleWorkItem(工作项目); 其中工作项目重新presents的异步操作 Func键<的CancellationToken,任务> 。造成这种情况的根源可以发现 rel=\"nofollow\">。

正如你所看到的 SheduleWorkItem 还有我们在工作项目变量异步操作,它实际上随后调用 ThreadPool.UnsafeQueueUserWorkItem 。这就要求 RunWorkItemImpl ,它使用异步等待 - 因此,你不需要在你的顶层,你不应该因为它再次对你的管理。

 公共无效ScheduleWorkItem(Func键<的CancellationToken,任务>的工作项目){
        Debug.Assert的(工作项目!= NULL);        如果(_cancellationTokenHelper.IsCancellationRequested){
            返回; //我们不会运行该工作项目
        }        //不安全*,因为我们想摆脱校长和其他结构的特定于当前的ExecutionContext
        ThreadPool.UnsafeQueueUserWorkItem(州=> {
            锁定(本){
                如果(_cancellationTokenHelper.IsCancellationRequested){
                    返回; //我们不会运行该工作项目
                }
                其他{
                    _numExecutingWorkItems ++;
                }
            }            RunWorkItemImpl((Func键<的CancellationToken,任务>)的状态);
        },工作项目);
    }    //我们可以使用异步无效在这里,因为我们要保证是关闭AspNetSynchronizationContext
    私人异步无效RunWorkItemImpl(Func键<的CancellationToken,任务>的工作项目){
        任务returnedTask = NULL;
        尝试{
            returnedTask =工作项目(_cancellationTokenHelper.Token);
            等待returnedTask.ConfigureAwait(continueOnCapturedContext:假);
        }
        赶上(例外前){
            // ----引起由返回的任务异常被取消
            如果(returnedTask = NULL&放大器;!&安培; returnedTask.IsCanceled){
                返回;
            }            // ----异常引起CancellationToken.ThrowIfCancellationRequested()
            OperationCanceledException operationCanceledException =前为OperationCanceledException;
            如果(operationCanceledException = NULL&放大器;!&安培; operationCanceledException.CancellationToken == _cancellationTokenHelper.Token){
                返回;
            }            _logCallback(AppDomain.CurrentDomain,除息); //方法不应该抛出
        }
        最后{
            WorkItemComplete();
        }
    }

有一个更深入的内部的这里

What is the benefit of using async with the ASP.NET QueueBackgroundWorkItem method?

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

My understanding is that async functions are used to prevent long-running tasks from blocking the main thread. However, in this case aren't we executing the task in its own thread anyway? What is the advantage over the non-async version:

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

解决方案

What is the benefit of using async with the ASP.NET QueueBackgroundWorkItem method?

Short answer

There is no benefit, in fact you shouldn't use async here!

Long answer

TL;DR

There is no benefit, in fact -- in this specific situation I would actually advise against it. From MSDN:

Differs from a normal ThreadPool work item in that ASP.NET can keep track of how many work items registered through this API are currently running, and the ASP.NET runtime will try to delay AppDomain shutdown until these work items have finished executing. This API cannot be called outside of an ASP.NET-managed AppDomain. The provided CancellationToken will be signaled when the application is shutting down.

QueueBackgroundWorkItem takes a Task-returning callback; the work item will be considered finished when the callback returns.

This explanation loosely indicates that it's managed for you.

According to the "remarks" it supposedly takes a Task returning callback, however the signature in the documentation conflicts with that:

    public static void QueueBackgroundWorkItem(
        Action<CancellationToken> workItem
    )

They exclude the overload from the documentation, which is confusing and misleading -- but I digress. Microsoft's "Reference Source" to the rescue. This is the source code for the two overloads as well as the internal invocation to the scheduler which does all the magic that we're concerned with.

Side Note

If you have just an ambiguous Action that you want to queue, that's fine as you can see they simply use a completed task for you under the covers, but that seems a little counter-intuitive. Ideally you will actually have a 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);
    }

Ultimately you end up with scheduler.ScheduleWorkItem(workItem); where the workItem represents the asynchronous operation Func<CancellationToken, Task>. The source for this can be found here.

As you can see SheduleWorkItem still has our asynchronous operation in the workItem variable, and it actually then calls into ThreadPool.UnsafeQueueUserWorkItem. This calls RunWorkItemImpl which uses async and await -- therefore you do not need to at your top level, and you should not as again it's managed for you.

    public void ScheduleWorkItem(Func<CancellationToken, Task> 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<CancellationToken, Task>)state);
        }, workItem);
    }

    // we can use 'async void' here since we're guaranteed to be off the AspNetSynchronizationContext
    private async void RunWorkItemImpl(Func<CancellationToken, Task> 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();
        }
    }

There is an even more in-depth read on the internals here.

这篇关于为什么要使用异步与QueueBackgroundWorkItem?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆