web-dev-qa-db-ja.com

なぜQueueBackgroundWorkItemで非同期を使用するのですか?

ASP.NET asyncメソッドでQueueBackgroundWorkItemを使用する利点は何ですか?

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

私の理解では、非同期関数は、長時間実行されるタスクがメインスレッドをブロックするのを防ぐために使用されます。しかし、この場合、とにかく独自のスレッドでタスクを実行していませんか?非非同期バージョンに対する利点は何ですか?

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

ASP.NET QueueBackgroundWorkItemメソッドで非同期を使用する利点は何ですか?

これにより、バックグラウンドワークアイテムで非同期APIを呼び出すことができます。

私の理解では、非同期関数は、長時間実行されるタスクがメインスレッドをブロックするのを防ぐために使用されます。しかし、この場合、とにかく独自のスレッドでタスクを実行していませんか?

非同期メソッドの利点は、callingスレッドを解放することです。 ASP.NETには「メインスレッド」はありません。そのままの状態でスレッドとスレッドプールスレッドを要求するだけです。この場合、非同期のバックグラウンド作業項目によってスレッドプールスレッドが解放され、スケーラビリティが向上する可能性があります。

非非同期バージョンに対する利点は何ですか

または、次のように考えることができます。LongRunningOperationAsyncが自然に非同期の操作である場合、LongRunningOperationは、他の目的で使用される可能性のあるスレッドをブロックします。

14
Stephen Cleary

ASP.NET QueueBackgroundWorkItemメソッドで非同期を使用する利点は何ですか?

短い答え

メリットはありません。実際、ここではasyncを使用しないでください。

長い答え

TL; DR

実際、メリットはありません。この特定の状況では、私は実際にそれに対してアドバイスします。から [〜#〜] msdn [〜#〜]

ASP.NETは、このAPIを介して登録された作業項目の数を追跡できるという点で、通常のThreadPool作業項目とは異なり、ASP.NETランタイムは、これらの作業項目の実行が完了するまでAppDomainのシャットダウンを遅らせようとします。このAPIは、ASP.NETで管理されているAppDomainの外部で呼び出すことはできません。提供されたCancellationTokenは、アプリケーションがシャットダウンするときに通知されます。

QueueBackgroundWorkItemは、タスクを返すコールバックを受け取ります。コールバックが戻ると、作業項目は終了したと見なされます。

この説明は、それがあなたのために管理されていることを大まかに示しています。

「備考」によると、おそらくTaskを返すコールバックが必要ですが、ドキュメントの署名はそれと矛盾します。

_public static void QueueBackgroundWorkItem(
    Action<CancellationToken> workItem
)
_

それらはドキュメントから過負荷を除外します。これは混乱を招き、誤解を招く可能性がありますが、私は逸脱します。 Microsoftの "参照ソース" 救助に。これは、2つのオーバーロードのソースコードであり、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>_を表します。これのソースは見つけることができます ここ

ご覧のとおり、SheduleWorkItemはまだworkItem変数に非同期操作があり、実際には_ThreadPool.UnsafeQueueUserWorkItem_を呼び出します。これにより、RunWorkItemImplasyncを使用するawaitが呼び出されます。したがって、最上位にいる必要はなく、管理する必要もありません。

_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();
    }
}
_

内部についてはさらに詳細な説明があります ここ

3
David Pine

マークされた回答のQueueBackgroundWorkItemに関する情報は素晴らしいですが、結論は間違っています。

クロージャーで非同期を使用すると、実際にはQueueBackgroundWorkItem(Func workItem)オーバーライドが使用され、タスクが終了するのを待ち、スレッドを保持せずに実行します。

したがって、これに対する答えは、async/awaitを使用して、workItemクロージャで任意の種類のIO操作を実行する場合です。

3
user6932954