非同期のTask <T>メソッドを同期的に実行するにはどうすればよいですか?


628

私は非同期/待機について学習していて、非同期メソッドを同期的に呼び出す必要がある状況に出くわしました。どうやってやるの?

非同期メソッド:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

通常の使用法:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

私は以下を使ってみました:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

ここからも提案を試みましたが、ディスパッチャーが一時停止状態の場合は機能しません。

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

呼び出しからの例外とスタックトレースはRunSynchronously次のとおりです。

System.InvalidOperationException

メッセージ:RunSynchronouslyは、デリゲートにバインドされていないタスクでは呼び出せません。

InnerException:null

ソース:mscorlib

StackTrace

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

46
「非同期メソッドを同期的に呼び出すにはどうすればよいですか」という質問に対する最良の答えは、「しない」です。強制的に動作させるハックはありますが、すべて微妙な落とし穴があります。代わりに、これを行う必要があるコードをバックアップして修正してください。
Stephen Cleary 2013年

57
@Stephen Cleary完全に同意しますが、コードがasync / awaitを使用しないいくつかのサードパーティAPIに依存している場合など、単に避けられない場合もあります。さらに、MVVMの使用時にWPFプロパティにバインドする場合、非同期/待機をプロパティでサポートしていないため、文字どおり非同期を使用することはできません。
Contango 2014

3
@StephenCleary常にではありません。GeneXusにインポートされるDLLを構築しています。async / awaitキーワードをサポートしていないため、同期メソッドのみを使用する必要があります。
Dinei

5
@StephenCleary 1)GeneXusは3番目のptツールであり、そのソースコードにアクセスできません。2)GeneXusには「関数」の実装さえないので、このタイプのもので「コールバック」を実装する方法を理解できません。確かに、Task同期的に使用するよりも難しい回避策になります。3)GeneXusをMongoDB C#ドライバーと統合しています。これにより、一部のメソッドが非同期でのみ公開されます
Dinei

1
@ygoe:などの非同期互換ロックを使用しSemaphoreSlimます。
Stephen Cleary 2016

回答:


456

ここで私が見つけた回避策は、すべてのケース(ディスパッチャの停止を含む)で機能します。それは私のコードではなく、私はそれを完全に理解するように努力していますが、うまくいきます。

次のようにして呼び出すことができます:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

ここからコードです

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

28
これがどのように機能するかについてのいくつかの背景として、Stephen Toub(Mr Parallel)がこれに関する一連の投稿を書きました。パート1 パート2 パート3
Cameron MacFarland

18
ジョンのコードを更新して、タスクをラムダでラップせずに動作するようにしました:github.com/tejacques/AsyncBridge。基本的に、usingステートメントを使用して非同期ブロックを操作します。usingブロック内のすべてが非同期で発生し、最後に待機します。欠点は、自分でタスクをコールバックでアンラップする必要があることですが、特に複数の非同期関数を一度に呼び出す必要がある場合は、かなり洗練されています。
トムジャック

17
@StephenCleary私は一般的に、コードは時々あなたは1が実行不可能な状況に自分自身を見つける、ダウンすべての方法asyncを指定しなければならないことをあなたに同意しますが持っている同期呼び出しとしてそれを強制します。基本的に、私の状況は、すべてのデータアクセスコードが非同期になっていることです。サイトマップに基づいてサイトマップを作成する必要があり、使用していたサードパーティのライブラリはMvcSitemapでした。これで、DynamicNodeProviderBase基本クラスを介して拡張しているとき、asyncメソッドとして宣言することはできません。新しいライブラリに置き換えるか、単に同期演算を呼び出す必要がありました。
justin.lovell 2014年

6
@ justin.lovell:はい、ライブラリの制限により、少なくともライブラリが更新されるまで、ハッキングを強いられる可能性があります。MvcSitemapはハッキングが必要な状況の1つ(MVCフィルターと子アクションも)のようです。このようなハック必要のないときにあまりにも頻繁に使用されるため、一般にこれを人々に思いとどまらせます。特にMVCでは、一部のASP.NET/MVC APIがを持っていると想定しているためAspNetSynchronizationContext、これらのAPIを呼び出している場合、この特定のハックは機能しません。
スティーブンクリアリー2014年

5
このコードは機能しません。プールスレッドから呼び出された場合、スレッド枯渇デッドロックをトリガーできます。呼び出し元は、操作が完了するのを待つことをブロックします。これは、スレッドプールを使い果たした場合には起こり得ません。この記事を参照してください
ZunTzu、2015年

318

助言されるこの回答は3歳です。私は、主に.Net 4.0での経験に基づいて、4.5での経験、特にasync-await。一般的に言ってそれは素晴らしいシンプルな解決策ですが、時々物事を壊します。コメント欄での議論を読んでください。

.Net 4.5

これを使うだけです:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

参照: TaskAwaiterTask.ResultTask.RunSynchronously


.Net 4.0

これを使って:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...またはこれ:

task.Start();
task.Wait();

67
.Result特定のシナリオでデッドロックを生成する可能性がある
ジョーディランゲン

122
Resultブログで説明しているように、コードデッドロックが発生するasync可能性があります。
Stephen Cleary 2013

8
@StephenCleary私はあなたの投稿を読んで、自分で試してみました。私は正直に言って、マイクロソフトの誰かが本当に酔っていたと思います...これは
winforms

9
質問は、非同期メソッドによって返されるタスクに関するものです。そのような種類のタスクは既に開始、実行、またはキャンセルされている可能性があるため、Task.RunSynchronouslyメソッドを使用するとInvalidOperationExceptionが発生する可能性があります。MSDNページ:Task.RunSynchronously Methodを参照してください。さらに、そのTaskはおそらくTask.Factory.StartNewまたはTask.Runメソッド(asyncメソッド内)によって作成されるため、再度開始するのは危険です。実行時に一部の競合状態が発生する可能性があります。一方で、Task.WaitTask.Resultはデッドロックを引き起こす可能性があります。
sgnsajgon 14

4
同期的に実行してください...何か欠落しているかどうかはわかりませんが、これはマークされた回答の恐怖よりも好ましいようです-停止するコードをテストするために非同期をオフにする方法を探していましたぶら下げからのUI
JonnyRaa 14

121

これには誰も驚かなかった:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

ここにある他のいくつかの方法ほどきれいではありませんが、次の利点があります。

  • 例外を飲み込みません(などWait
  • AggregateException(などResult)でスローされた例外はラップしません
  • 両方Taskで機能し、Task<T>自分で試してみてください!

また、GetAwaiterはダック型であるため、タスクだけでなく、非同期メソッド(ConfiguredAwaitableまたはなどYieldAwaitable)から返されるすべてのオブジェクトで機能します。


編集:このアプローチ(またはを使用.Result)がデッドロックする可能性があることに注意してください.ConfigureAwait(false)。待機するたびに、BlahAsync()直接アクセスする可能性のあるすべての非同期メソッド(直接呼び出すものだけでなく)を追加するようにしてください。説明

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

.ConfigureAwait(false)どこにでも追加するのが面倒で、パフォーマンスを気にしない場合は、代わりに行うことができます

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

1
シンプルなもので私のために働く。また、メソッドがIAsyncOperationを返す場合は、まずそれをタスクに変換する必要がありました:BlahAsync()。AsTask()。GetAwaiter()。GetResult();
リーマクファーソン

3
これにより、asmx Webメソッド内でデッドロックが発生しました。それでも、メソッド呼び出しをTask.Run()でラップすると機能しました:Task.Run(()=> BlahAsync())。GetAwaiter()。GetResult()
Augusto Barreto

このアプローチは、ラムダを含まないため、構文的に最も気に入っています。
dythim

25
自分へのリンクを挿入するために他の人の回答を編集しないでください。あなたの答えが良いと思うなら、代わりにコメントとして残してください。
レイチェル

1
docs.microsoft.com/en-us/dotnet/api/…GetAwaiter()、「このメソッドは、コードで直接使用するのではなく、コンパイラユーザーを対象としています」と述べています。
Theophilus

75

スケジューラをだまして同期的に実行するよりも、スレッドプールでタスクを実行する方がはるかに簡単です。これにより、デッドロックが発生しないことを確認できます。コンテキストの切り替えにより、パフォーマンスが影響を受けます。

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

3
次にtask.Wait()を呼び出します。データ型は単にTaskです。
マイケルLペリー14

1
DoSomethingAsync()は全体として長時間実行の非同期メソッド(内部的には長時間実行タスクを待機している)であると仮定しましょう。ただし、フロー制御は呼び出し元にすばやく返されるため、ラムダ引数の作業もすばやく終了します。Tusk.Run()の結果はTask <Task>またはTask <Task <>>になる可能性があるため、すぐに完了する外部タスクの結果を待っていますが、内部タスク(非同期メソッドで長時間実行されるジョブを待機しているため)まだ実行中です。結論として、非同期メソッドの同期動作を実現するには、おそらくUnwrap ()アプローチ(@ J.Lennonの投稿で行われたを使用する必要があります。
sgnsajgon 2014

5
@sgnsajgonあなたは間違っています。Task.Runは、結果を自動的にラップ解除するという点でTask.Factory.StartNewとは異なります。この記事を参照してください
ZunTzu 2015年

1
Task.Run(DoSomethingAsync)代わりに書くことはできますか?これにより、1レベルのデリゲートが削除されます。
ygoe 2017

1
うん。ただし、反対の方向に進むと、Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());より明確になり、@ sgnsajgonがTask <Task <MyResult >>を返す可能性があるという懸念に対処します。Task.Runの正しいオーバーロードはどちらの方法でも選択されますが、非同期デリゲートによって意図が明確になります。
マイケルLペリー

57

私は非同期/待機について学習していて、非同期メソッドを同期的に呼び出す必要がある状況に出くわしました。どうやってやるの?

最善の答えは、「状況」が何であるかに依存する詳細で、そうしないことです

プロパティのゲッター/セッターですか?ほとんどの場合、「非同期プロパティ」よりも非同期メソッドを使用することをお勧めします。(詳細については、非同期プロパティに関する私のブログ投稿を参照しください)。

これはMVVMアプリであり、非同期データバインディングを実行しますか?次に、非同期データバインディングに関するMSDNの記事でNotifyTask説明されているように、私のようなものを使用します

コンストラクタですか?次に、おそらく非同期ファクトリメソッドを検討します。(詳細については、非同期コンストラクターに関する私のブログ投稿を参照してください)。

ほとんどの場合、sync-over-asyncよりも良い答えがあります。

あなたの状況でそれができない場合(そして、状況をここで説明する質問をすることでこれを知っている場合)、同期コードを使用することをお勧めします。非同期が最も良い方法です。同期は2番目に優れています。Sync-over-Asyncは推奨されません。

ただし、sync-over-asyncが必要な状況がいくつかあります。具体的には、呼び出しコードによって制約されているため、同期する必要があり(非同期を可能にするためにコードを再考したり再構築したりする方法はまったくありません)、非同期コードを呼び出す必要あります。これは非常にまれな状況ですが、時々発生します。

その場合は、ブラウンフィールドasync開発に関する私の記事で説明されているハックの1つを使用する必要があります。具体的には次のとおりです。

  • ブロッキング(例:)GetAwaiter().GetResult()これがデッドロックを引き起こす可能性があることに注意してください(私のブログで説明しています)。
  • スレッドプールスレッドでコードを実行する(例:)Task.Run(..).GetAwaiter().GetResult()。これが機能するのは、非同期コードをスレッドプールスレッドで実行できる場合のみです(つまり、UIまたはASP.NETコンテキストに依存していません)。
  • ネストされたメッセージループ。これは、非同期コードが特定のコンテキストタイプではなくシングルスレッドコンテキストのみを想定している場合にのみ機能することに注意してください(多くのUIおよびASP.NETコードは特定のコンテキストを想定しています)。

入れ子になったメッセージループは、再入可能性を引き起こすため、すべてのハッキングの中で最も危険です。再入可能性については非常に扱いが難しく、(IMO)はWindowsでのほとんどのアプリケーションバグの原因です。特に、UIスレッドで作業キューをブロックしている場合(非同期作業が完了するのを待機している場合)、CLRは実際にいくつかのメッセージポンプを実行します-実際には、Win32メッセージを処理しますコード。ああ、そしてあなたはどのメッセージ分かり ません - クリス・ブラムが「何が汲み上げられるかを正確に知るのは素晴らしいことではないでしょうか?残念ながら、ポンピングは致命的な理解を超えた黒人の芸術です。」、それから私たちは本当に知る望みがありません。

したがって、UIスレッドでこのようにブロックすると、問題が発生します。同じ記事からの別の引用:「社内または社外のお客様は、STA [UIスレッド]での管理されたブロック中にメッセージをポンプしていることに気づくことがあります。これは非常に難しいことを知っているため、これは正当な懸念事項です。再入可能性に直面しても堅牢なコードを書くために」

はい、そうです。再入可能性に直面しても堅牢なコードを書くのは非常に困難です。また、入れ子になったメッセージループは、再入可能性に直面しても堅牢なコードを記述することを強制します。これが、この質問に対する受け入れられた(そして最も支持された)回答が実際には非常に危険な理由です。

他のすべてのオプションが完全になくなった場合-コードを再設計できない、非同期になるように再構成できない-変更できない呼び出しコードを同期することを余儀なくされる-ダウンストリームコードを同期するように変更できない-ブロックすることはできません-非同期コードを別のスレッドで実行することはできません-そしてその場合にのみ、再入可能性の採用を検討してください。

このコーナーに気が付いた場合は、Dispatcher.PushFrameWPFアプリのApplication.DoEvents場合、WinFormアプリの場合はループ、一般的な場合は自分のものなど使用することをお勧めしますAsyncContext.Run


スティーブンは、別の非常に類似しているqestionあなたがあまりにも素晴らしい答えを提供します。それらの1つが重複として閉じられるか、マージ要求として閉じられるか、最初にメタで起動されると思いますか?提案?
アレクセイレベンコフ2017

1
@AlexeiLevenkov:いくつかの理由で、私はそうするのが適切ではないと思います:1)リンクされた質問の答えはかなり古くなっています。2)既存のSO Q / Aよりも完成度が高いと思うテーマについて、記事全体を書きました。3)この質問に対する受け入れられた回答は非常に人気があります。4)私はその受け入れられた答えに猛烈に反対しています。したがって、これをその重複として閉じることは、権力の濫用となるでしょう。これの重複(またはマージ)としてそれを閉じると、危険な答えがさらに強化されます。私はそれを許可し、コミュニティに任せます。
スティーブンクリアリー2017

OK。何らかの方法でメタで取り上げることを検討します。
アレクセイレベンコフ2017

9
この答えは私の頭のはるか上にあります。「ずっと下まで非同期で使用する」は、明らかに従うことができないため、混乱を招くアドバイスです。async Main()メソッドを持つプログラムはコンパイルされません。ある時点で、あなたはまし同期と非同期の世界の間のギャップを埋めるために。これは非常にまれな状況」ではなく、非同期メソッドを呼び出すすべてのプログラムで文字通り必要です。「sync-over-async」を行わないオプションはありません。現在書いているメソッドに負担をかけるのではなく、呼び出し側のメソッドに負担をかけるオプションだけです。
Mark Amery 2017年

1
すごい。asyncアプリケーションのすべてのメソッドを実装しようとしています。そして、それはたくさんあります。これをデフォルトにすることはできませんか?
ygoe 2017

25

私があなたの質問を正しく読んでいる場合-非同期メソッドへの同期呼び出しを必要とするコードは、中断されたディスパッチャスレッドで実行されています。そして、asyncメソッドが完了するまで、実際にそのスレッドを同期的にブロックしたいとします。

C#5の非同期メソッドは、フードの下でメソッドを効果的に細かく分割し、Taskシャバン全体の全体的な完了を追跡できるを返すことで強化されています。ただし、分割されたメソッドの実行方法は、await演算子に渡される式のタイプによって異なります。

ほとんどの場合await、typeの式で使用しますTask。タスクのawaitパターンの実装は「スマート」であり、に委ねられますSynchronizationContext。これにより、基本的に次のことが起こります。

  1. に入るスレッドawaitがDispatcherまたはWinFormsメッセージループスレッド上にある場合、非同期メソッドのチャンクがメッセージキューの処理の一部として発生することが保証されます。
  2. に入るawaitスレッドがスレッドプールスレッド上にある場合、asyncメソッドの残りのチャンクは、スレッドプール上の任意の場所で発生します。

そのため、おそらく問題が発生しています-非同期メソッドの実装は、一時停止されていても、残りをDispatcherで実行しようとしています。

.... バックアップ!....

私は質問しなければなりません、なぜ非同期メソッドで同期的にブロックしようとしているのですか?これを行うと、メソッドが非同期的に呼び出された理由に関する目的が無効になります。一般awaitに、DispatcherまたはUIメソッドの使用を開始するときは、UIフロー全体を非同期にする必要があります。たとえば、呼び出しスタックが次のようなものだったとします。

  1. [上] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFまたはWinFormsコード
  6. [メッセージループ] - WPFまたはWinFormsメッセージループ

次に、非同期を使用するようにコードを変換すると、通常は次のようになります。

  1. [上] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFまたはWinFormsコード
  6. [メッセージループ] - WPFまたはWinFormsメッセージループ

実際に答える

上記のAsyncHelpersクラスは、ネストされたメッセージループのように動作するため実際には機能しますが、Dispatcher自体で実行しようとするのではなく、独自の並列メカニズムをDispatcherにインストールします。これが問題の回避策の1つです。

別の回避策は、スレッドプールスレッドで非同期メソッドを実行し、それが完了するのを待つことです。これは簡単です。次のスニペットで実行できます。

var customerList = TaskEx.RunEx(GetCustomers).Result;

最終的なAPIはTask.Run(...)ですが、CTPではExサフィックスが必要になります(ここでの説明)。


詳細な説明は+1ですがTaskEx.RunEx(GetCustomers).Result、一時停止されたディスパッチャスレッドで実行されるとアプリケーションがハングします。また、GetCustomers()メソッドは通常非同期で実行されますが、ある状況では同期的に実行する必要があるため、メソッドの同期バージョンを構築せずにそれを行う方法を探していました。
レイチェル

「なぜ非同期メソッドを同期的にブロックしようとしているのですか?」の+1 asyncメソッドを適切に使用する方法は常にあります。ネストされたループは避けてください。
スティーブンクリアリー2013

24

これは私にとってはうまくいきます

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

また、使用する必要がTask.Unwrapのあなたのため、この方法をTask.Wait文は(によって作成された外側のタスクを待っ起こすTask.Runない内側のため、)のawaitトンタスクは、拡張メソッドのパラメータとして渡されました。あなたのTask.Runのメソッドが返すないタスク<T>が、タスク<タスク<T >>。いくつかの単純なシナリオでは、たとえば、TryExecuteTaskInlineメソッドを使用して待機操作中に現在のスレッド内でタスクを実行するなど、TaskSchedulerの最適化が原因でソリューションが機能する場合があります。この回答に対する私のコメントをご覧ください。
sgnsajgon 2014

1
不正解です。Task.RunはTask <T>を返します。このオーバーロードを参照msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement

これはどのように使用されることになっていますか?このWPFのデッドロック:MyAsyncMethod().RunTaskSynchronously();
ygoe '26

18

UIスレッドをブロックせずに同期的にタスクを実行することがわかった最も簡単な方法は、次のようにRunSynchronously()を使用することです。

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

私の場合、何かが発生したときに発生するイベントがあります。何回起こるかわかりません。したがって、私はイベントで上記のコードを使用しているので、それが起動するたびにタスクを作成します。タスクは同期的に実行され、私にとってはうまくいきます。それがどれほど単純であるかを考えると、これを見つけるのに非常に長い時間がかかって驚いただけでした。通常、推奨事項ははるかに複雑でエラーが発生しやすくなります。シンプルで綺麗でした。


1
しかし、非同期コードが必要なものを返すときに、このメソッドをどのように使用できますか?
S.Serpooshan 2018

16

私は何度か、主に単体テストやWindowsサービス開発で直面しました。現在、私は常にこの機能を使用しています:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

シンプルで簡単で問題はありませんでした。


これは私にとってデッドロックしなかった唯一のものです。
AndreFeijo 2017

15

このコードはMicrosoft.AspNet.Identity.Coreコンポーネントで見つかりました。

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

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}


13

ほんの少し注意-このアプローチ:

Task<Customer> task = GetCustomers();
task.Wait()

WinRTで動作します。

説明させてください:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

さらに、このアプローチはWindowsストアソリューションでのみ機能します。

注:この方法は、(@ Servyのコメントによると)他の非同期メソッドの内部でメソッドを呼び出す場合、スレッドセーフではありません。


私はこの解決策を説明しました、編集セクションを確認してください。
RredCat 2013

2
これは、非同期の状況で呼び出されたときに非常に簡単にデッドロックを引き起こす可能性があります。
2013

@Servyは理にかなっています。それで、Wait(timeOut)を使用して正しくなると、役に立ちますか?
RredCat 2013

1
次に、操作が実際に行われていないときにタイムアウトに到達することを心配する必要があります。これは非常に悪いことです。また、デッドロックが発生する場合(さらに、その場合継続する場合)、タイムアウトまで待機するのにかかる時間も心配する必要があります。それが行われていないときにオン)。だから、それは問題を解決しません。
2013

@Servy CancellationTokenソリューションを実装する必要があるようです。
RredCat 2013

10

コードでは、タスクの実行を最初に待機しますが、タスクを開始していないため、無期限に待機します。これを試して:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

編集:

例外が発生すると言います。スタックトレースを含む詳細を投稿してください。
Monoに、次のテストケースが含まれています。

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

これが機能するかどうかを確認します。そうでない場合は、非常にまれですが、非同期CTPの奇妙なビルドがある可能性があります。それが機能する場合は、コンパイラーが正確に何を生成するか、およびTaskインスタンス化がこのサンプルとどのように異なるかを調べることができます。

#2を編集:

私はときに説明した例外が発生したことをリフレクターでチェックm_actionですnull。これはちょっと変ですが、私は非同期CTPの専門家ではありません。私が言ったように、あなたはあなたのコードを逆コンパイルし、Taskそれm_actionがどのようになって来るのか、どれほど正確にインスタンス化されているのかを見るべきですnull


PS時々の反対投票との取り決めは何ですか?詳しく説明しますか?


質問を調整して、試行したコードを少しわかりやすくしました。RunSynchronouslyはのエラーを返しますRunSynchronously may not be called on a task unbound to a delegate。結果はすべて中国語で表示されるため、Googleは役に立ちません...
Rachel

違いは、タスクを作成せずに実行しようとすることです。代わりに、awaitキーワードが使用されると、タスクはasyncメソッドによって作成されます。私の以前のコメントに投稿された例外は私が受け取る例外ですが、Googleが原因や解決策を見つけることができない数少ないものの1つです。
レイチェル

1
asyncそしてasyncキーワードは、より多くのシンタックスシュガーよりも何もありません。コンパイラーが作成するコードを生成Task<Customer>するGetCustomers()ので、ここで最初に調べます。例外については、例外メッセージのみを投稿しました。これは、例外タイプとスタックトレースなしでは役に立たないものです。例外のToString()メソッドを呼び出し、質問に出力を投稿します。
Dan Abramov

@gaearon:元の質問で例外の詳細とスタックトレースを投稿しました。
レイチェル

2
@gaearonあなたの投稿は質問に適用できないため、反対票があったと思います。議論は、非同期の待機メソッドについてであり、単純なタスクを返すメソッドについてではありません。さらに、私の意見では、非同期待機メカニズムは構文糖衣ですが、それほど簡単ではありません。継続、コンテキストキャプチャ、ローカルコンテキストの再開、ローカル例外処理の強化などがあります。次に、非同期メソッドは定義上、現在少なくともスケジュールされているタスクを返し、複数回が実行状態であるため、asyncメソッドの結果に対してRunSynchronouslyメソッドを呼び出さないでください
sgnsajgon 2014

9

.Net 4.6でテスト済み。デッドロックを回避することもできます。

非同期メソッドがを返す場合Task

Task DoSomeWork();
Task.Run(async () => await DoSomeWork()).Wait();

非同期メソッドを返す場合 Task<T>

Task<T> GetSomeValue();
var result = Task.Run(() => GetSomeValue()).Result;

編集

呼び出し元がスレッドプールスレッドで実行されている場合(または呼び出し元もタスク内にある場合)は、状況によってはデッドロックが発生する可能性があります。


1
ほぼ8年後の私のanswar :) 2番目の例-主に使用されるすべてのスケジュールされたコンテキスト(コンソールアプリ/ .NETコア/デスクトップアプリ/ ...)でデッドロックを生成します。ここで、私が今話していることの概要を説明します。medium.com
rubrikkgroup

Result同期呼び出しが必要な場合はジョブに最適で、それ以外の場合はまったく危険です。名前ResultやインテリセンスにResultは、それがブロッキングコールであることを示すものは何もありません。本当に名前を変更する必要があります。
Zodman

5

以下のコードを使用してください

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));

4

次のような呼び出しを作成しませんか?

Service.GetCustomers();

それは非同期ではありません。


4
これを機能させることができない場合は、これで対処します...非同期バージョンに加えて同期バージョンを作成します
Rachel

3

この回答は、WPF for .NET 4.5を使用しているすべての人を対象としています。

関数定義にキーワードがない場合Task.Run()、GUIスレッドで実行しようとすると、task.Wait()無期限にハングasyncします。

この拡張メソッドは、GUIスレッド上にあるかどうかを確認し、そうである場合はWPFディスパッチャースレッド上でタスクを実行することで問題を解決します。

このクラスは、MVVMプロパティや、async / awaitを使用しない他のAPIへの依存関係など、不可避な状況でasync / awaitワールドとnon-async / awaitワールドの間の接着剤として機能することができます。

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}

3

多くの人がコメントで述べているように、単に呼び出す.Result;.Wait()、デッドロックのリスクです。私たちの多くはワンライナーが好きなので、これらを次の用途に使用できます.Net 4.5<

非同期メソッドを介して値を取得する:

var result = Task.Run(() => asyncGetValue()).Result;

非同期メソッドを同期的に呼び出す

Task.Run(() => asyncMethod()).Wait();

の使用によるデッドロックの問題は発生しませんTask.Run

ソース:

https://stackoverflow.com/a/32429753/3850405


1

次のヘルパーメソッドも問題を解決できると思います。

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

次のように使用できます。

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

1
投票について説明してください
donttellya

2
...私はまだこの回答が反対票を投じられた理由にまだ熱心に関心がありますか?
donttellya

それは本当の「同期」ではありません。2つのスレッドを作成し、他の最初の結果を待ちます。
tmt '18年

とにかく、これは非常に悪い考えです。
Dan Pantry、2016

1
ほぼ同じコード(行ごとに同じ)を記述しただけですが、代わりに自動リセットイベントの代わりにSemaphoreSlimを使用しています。もっと早く見たかったのに。私はこのアプローチがデッドロックを防止し、非同期コードを真の非同期シナリオと同じように実行し続けることを見つけました。なぜこれが悪い考えなのか本当にわからない。私が上で見た他のアプローチよりもはるかにきれいに思えます。
tmrog 2017年

0

これは私の作品です

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

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

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

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

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}

-1

私は、SpinWaitがこれに対してかなりうまく機能することを発見しました。

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

上記のアプローチでは、.Resultまたは.Wait()を使用する必要はありません。また、タスクが完了しない場合に永久にスタックしないように、タイムアウトを指定することもできます。


1
反対票は、誰かがこの方法を好まないことを示唆しています。これの欠点についてコメントできる人はいますか?
Grax32

なぜ反対票が与えられたのかという反対投票者がいない場合、誰かが反対投票することはできますか?:-)
カーティス、

1
これはポーリング(スピン)であり、デリゲートは1秒あたり最大1000回、プールからスレッドを取得します。タスクが終了した直後に制御を返さない場合があります(最大10 + msエラー)。タイムアウトによって終了した場合、タスクは実行を継続するため、タイムアウトは実質的に役に立たなくなります。
Sinatr

実際、これをコードのあらゆる場所で使用しており、条件が満たされると、SpinWaitSpinUntil()はすぐに終了します。したがって、「条件が満たされた」またはタイムアウトのどちらが先でも、タスクは終了します。実行し続けません。
カーティス

-3

wp8の場合:

包んでください:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

あれを呼べ:

GetCustomersSynchronously();

3
いや、この文句を言わない仕事、タスクは、コンストラクタ(そのデリゲートではなくタスク...)からデリゲートを待たれていないため
プエルトリコスーター

-4
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }

-5

または、次のようにすることもできます。

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

これをコンパイルするには、拡張アセンブリを参照してください。

System.Net.Http.Formatting

-9

それが私のために働く次のコードを試してください:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.