web-dev-qa-db-ja.com

C#で同期メソッドから非同期メソッドを呼び出す方法

同期メソッドから呼び出したいpublic async void Foo()メソッドがあります。これまでのところ、MSDNの資料から見たことは、非同期メソッドを介して非同期メソッドを呼び出すことだけでしたが、私のプログラム全体は非同期メソッドを使用して構築されていません。

これでも可能ですか?

非同期メソッドからこれらのメソッドを呼び出す1つの例を次に示します。 http://msdn.Microsoft.com/ja-jp/library/hh300224(v=vs.110).aspx

今私は、同期メソッドからこれらの非同期メソッドを呼び出すことを検討しています。

628
Tower

非同期プログラミングはコードベースを通じて「成長」します。 ゾンビウイルスと比較して 。最善の解決策はそれを成長させることですが、時にはそれが不可能な場合もあります。

私は部分的に非同期のコードベースを扱うために私の Nito.AsyncEx ライブラリにいくつかの型を書きました。しかし、あらゆる状況で機能する解決策はありません。

解決策A

コンテキストに同期し直す必要がない単純な非同期メソッドがある場合は、Task.WaitAndUnwrapExceptionを使用できます。

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

notは、AggregateExceptionで例外をラップするため、Task.WaitまたはTask.Resultを使用します。

この解決法は、MyAsyncMethodがそのコンテキストに同期し直さない場合にのみ適切です。つまり、await内のすべてのMyAsyncMethodConfigureAwait(false)で終わる必要があります。つまり、UI要素を更新したり、ASP.NETの要求コンテキストにアクセスしたりすることはできません。

解決策B

MyAsyncMethodがそのコンテキストに同期し直す必要がある場合は、AsyncContext.RunTaskを使用してネストされたコンテキストを提供できます。

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* 2014年4月14日更新:最新バージョンのライブラリでは、APIは次のとおりです。

var result = AsyncContext.Run(MyAsyncMethod);

(この例では、RunTaskTask例外を伝搬するため、Task.Resultを使用しても構いません)。

あなたがAsyncContext.RunTaskの代わりにTask.WaitAndUnwrapExceptionを必要とするかもしれない理由はWinForms/WPF/SL/ASP.NETで起こるかなり微妙なデッドロックの可能性のためです:

  1. 同期メソッドは非同期メソッドを呼び出し、Taskを取得します。
  2. 同期メソッドはTask上でブロッキング待ちをします。
  3. asyncメソッドはawaitなしでConfigureAwaitを使用します。
  4. Taskasyncメソッドが終了したときにのみ完了するため、この状況では完了できません。 asyncメソッドはSynchronizationContextへの継続をスケジュールしようとしているため完了できません。また、WinForms/WPF/SL/ASP.NETは、同期メソッドが既にそのコンテキストで実行されているため継続を実行できません。

これが、すべてのasyncメソッド内で可能な限りConfigureAwait(false)を使用することをお勧めする理由の1つです。

解決策C

AsyncContext.RunTaskはすべてのシナリオで機能するわけではありません。たとえば、asyncメソッドが、UIイベントの完了を必要とするものを待っている場合は、ネストされたコンテキストでもデッドロックになります。その場合、スレッドプールでasyncメソッドを起動できます。

var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

ただし、このソリューションではスレッドプールのコンテキストで機能するMyAsyncMethodが必要です。そのため、UI要素を更新したり、ASP.NETの要求コンテキストにアクセスしたりすることはできません。その場合は、awaitステートメントにConfigureAwait(false)を追加して解決策Aを使用することもできます。

更新、2019-05-01: 現在の "最悪最悪の慣行"は MSDNの記事にあります

538
Stephen Cleary

最終的に私の問題を解決した解決策を追加すると、誰かの時間を節約できることを願っています。

最初に Stephen Cleary の記事をいくつか読んでください:

「非同期コードでブロックしない」の「2つのベストプラクティス」から、最初の方法は機能せず、2番目の方法は適用できませんでした(基本的にawaitを使用できる場合は、機能します! )。

だからここに私の回避策があります:Task.Run<>(async () => await FunctionAsync());の内側に呼び出しをラップし、うまくいけばdeadlockはもうありません。

ここに私のコードがあります:

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}
234
Tohid

MicrosoftはAsyncHelper(内部)クラスを構築して、AsyncをSyncとして実行しました。ソースは次のようになります。

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Microsoft.AspNet.Identity基本クラスにはAsyncメソッドのみがあり、それらをSyncとして呼び出すために、次のような拡張メソッドを持つクラスがあります(使用例)。

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

コードのライセンス条件について懸念がある方のために、MicrosoftによってMITライセンスされていることを示すコメントがある非常に類似したコードへのリンクがあります(スレッドにカルチャのサポートを追加するだけです)。 https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

182
Erik Philips

async MainはC#7.2の一部となり、プロジェクトの高度なビルド設定で有効にすることができます。

C#<7.2の場合、正しい方法は次のとおりです。

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

これは、多くのMicrosoftのドキュメントで使用されています。たとえば、次のようになります。 https://docs.Microsoft.com/ja-jp/Azure/service-bus-messaging/service-bus-dotnet-how-to- use-topics-subscriptions

107
Lee Smith
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

'await'というキーワードを「この長時間実行タスクを開始してから呼び出し元のメソッドに制御を戻す」と読みます。長期実行タスクが完了すると、それはその後にコードを実行します。待機後のコードは、以前CallBackメソッドだったものと似ています。論理フローという大きな違いは中断されないため、書き込みと読み取りがはるかに簡単になります。

46
Despertar

100%確信が持てませんが、 このブログ に記載されている手法はさまざまな状況で機能するはずです。

この伝播ロジックを直接呼び出したい場合は、task.GetAwaiter().GetResult()を使用できます。

31
NStuke

最も受け入れられている答えは完全には正しくありません。そこに IS あらゆる状況で機能する解決策:特別メッセージポンプ(SynchronizationContext)。

Async関数から呼び出されたすべての継続が、呼び出しスレッドで実行されているアドホックSynchronizationContext(メッセージポンプ)にマーシャリングされるため、デッドロックしないようにしながら、呼び出しスレッドは期待どおりブロックされます。

アドホックメッセージポンプヘルパーのコード:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

使用法:

AsyncPump.Run(() => FooAsync(...));

非同期ポンプのより詳細な説明は利用可能です ここ

23
Robert J

もうこの質問に注目している人には….

Microsoft.VisualStudio.Services.WebApiを見ると、TaskExtensionsというクラスがあります。そのクラス内には、静的拡張メソッドTask.SyncResult()があります。これは、タスクが戻るまでスレッドを完全にブロックするようなものです。

内部的には非常に単純なtask.GetAwaiter().GetResult()を呼び出しますが、asyncTask<T>またはTask<HttpResponseMessage>...を返すTaskメソッドを処理するためにオーバーロードされています。

...GetAwaiter().GetResult()は、ブロッキングコンテキストで非同期コードを実行するためのMSの公式な方法のようです。私のユースケースではとてもうまくいくようです。

5
jrypkahauer

同期コードから任意の非同期メソッドを呼び出すことができます。つまり、それらに対してawaitが必要になるまでです。その場合は、それらもasyncとマークする必要があります。

多くの人がここで提案しているように、同期メソッドの結果のタスクに対してWait()またはResultを呼び出すことができますが、その場合はそのメソッドでブロッキング呼び出しが発生し、非同期の目的が無効になります。

メソッドをasyncにすることはできませんし、同期メソッドをロックしたくない場合は、タスクのContinueWithメソッドにパラメータとして渡してコールバックメソッドを使用する必要があります。

3
base2
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

またはこれを使う:

var result=result.GetAwaiter().GetResult().AccessToken
2
rajesh A

私は私がとても遅れているのを知っています。しかし、私のような人が、他の図書館に頼らずに、きちんとした簡単な方法でこれを解決したいと思った場合。

次のコードが見つかりました コードの一部 from Ryan

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

それならあなたはこのように呼ぶことができます

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
2
Wahid Bitar