1154

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

これも可能ですか?

非同期メソッドからこれらのメソッドを呼び出す1つの例を次に示します。
ウォークスルー:AsyncとAwaitを使用してWebにアクセスする(C#およびVisual Basic)

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

4

15 に答える 15

945

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

部分的に非同期のコード ベースを処理するために、 Nito.AsyncExライブラリにいくつかの型を記述しました。ただし、すべての状況で機能するソリューションはありません。

ソリューション A

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

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

またはで例外をラップするため、orを使用したくありませTask.WaitTask.ResultAggregateException

MyAsyncMethodこのソリューションは、 がそのコンテキストに同期されない場合にのみ適切です。つまり、すべてawaitの inMyAsyncMethodは で終わる必要がありConfigureAwait(false)ます。これは、UI 要素を更新したり、ASP.NET 要求コンテキストにアクセスしたりできないことを意味します。

ソリューション B

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

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

*2014 年 4 月 14 日更新: ライブラリの最近のバージョンでは、API は次のようになります。

var result = AsyncContext.Run(MyAsyncMethod);

(例外が伝播されるTask.Resultため、この例で使用しても問題ありません)。RunTaskTask

AsyncContext.RunTask代わりに必要な理由Task.WaitAndUnwrapExceptionは、WinForms/WPF/SL/ASP.NET で発生するかなり微妙なデッドロックの可能性があるためです。

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

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

ソリューション C

AsyncContext.RunTaskすべてのシナリオで機能するとは限りません。たとえばasync、UI イベントの完了を必要とする何かをメソッドが待機している場合、ネストされたコンテキストでもデッドロックが発生します。その場合、asyncスレッド プールでメソッドを開始できます。

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

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

更新、2019 年 5 月 1 日:現在の「最小ワースト プラクティス」は、こちらの MSDN 記事にあります

于 2012-02-18T18:06:00.493 に答える
65
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」キーワードは、「この長時間実行されるタスクを開始してから、呼び出し元のメソッドに制御を戻す」と読みます。長時間実行されるタスクが完了すると、その後にコードが実行されます。await の後のコードは、CallBack メソッドであったものに似ています。大きな違いは、論理フローが中断されないため、読み書きがはるかに簡単になります。

于 2012-02-18T17:55:26.763 に答える
61

100% 確信があるわけではありませんが、このブログで説明されている手法は多くの状況で機能するはずです。

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

于 2014-03-26T22:14:01.443 に答える
28

ただし、すべての状況で機能する優れたソリューションがあります (ほとんど: コメントを参照): アドホック メッセージ ポンプ (SynchronizationContext)。

呼び出しスレッドは期待どおりにブロックされますが、呼び出しスレッドで実行されているアドホック 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(...));

非同期ポンプの詳細については、こちらを参照してください。

于 2015-08-25T07:28:34.530 に答える
11

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

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

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

于 2012-02-18T17:54:47.437 に答える