web-dev-qa-db-ja.com

非同期タスク<T>メソッドを同期的に実行する方法

私はasync/awaitについて学んでいて、asyncメソッドを同期的に呼び出す必要がある状況に陥りました。どうやってやるの?

非同期メソッド

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)

私も here から提案を試みましたが、ディスパッチャがサスペンド状態になっていると動作しません。

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()
572
Rachel

これは私が見つけた回避策です(サスペンドされたディスパッチャを含む)すべての場合に有効です。それは私のコードではなく、私はまだそれを完全に理解するために働いています、しかしそれはうまくいきます。

これを使って呼び出すことができます。

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;
        }
    }
}
419
Rachel

忠告 この答えは3歳です。私は主に.Net 4.0での経験に基づいてそれを書きました、そして、特にasync-awaitとの4.5と非常に少しでした。一般的に言ってそれはいい単純な解決策ですが、時々物事を壊します。コメント内の議論を読んでください。

.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();
308
AK_

誰もこれに言及していませんでした:

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

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

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

  • Waitのように)例外を飲み込むことはありません
  • AggregateExceptionのように)Resultにスローされた例外をラップしません。
  • TaskTask<T>の両方で動作します( 自分で試してみてください!

また、GetAwaiterはアヒル型なので、タスクだけではなく、非同期メソッドから返されるすべてのオブジェクト(ConfiguredAwaitableYieldAwaitableなど)に対しても機能するはずです。


edit: あなたが待つたびに.ConfigureAwait(false)を追加することを確実にしない限り、BlahAsync()から到達できる可能性のあるすべての非同期メソッドに対して、このアプローチ(または.Resultを使用)がデッドロックする可能性がある直接呼び出すもの) 説明

// 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()
103
James Ko

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

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; 
72
Michael L Perry

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

最善の答えはしないで、詳細は「状況」に依存します。

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

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

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

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

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

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

その場合、 brownfield async development に関する私の記事で説明されているハックのいずれかを使用する必要があります。具体的には:

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

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

したがって、UIスレッドでこのようにブロックすると、トラブルが発生します。同じ記事からの別の引用:「社内または社外の顧客は、STA [UIスレッド]の管理されたブロッキング中にメッセージを送信していることを時々発見します。これは正当な懸念です。再入可能性に直面している堅牢なコードを書くこと。」

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

他のすべてのオプションから完全に外れている場合-コードを再設計することはできず、非同期に再構成することはできません-変更できない呼び出しコードを強制的に同期すること-ダウンストリームコードを変更して同期することはできません-ブロックできない-非同期コードを別のスレッドで実行することはできません-その後、そしてその後のみ再入可能性を考慮する必要があります。

このコーナーにいる場合は、 WPFアプリの場合はDispatcher.PushFrame のようなものを使用し、WinFormアプリの場合はApplication.DoEventsでループし、一般的な場合は自分の AsyncContext.Run をお勧めします。

50
Stephen Cleary

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

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;
    }
}
23
Clement

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

C#5の非同期メソッドは、効果的にメソッドを内部で細かく分割し、全体の完成を追跡することができるTaskを返すことによって強化されています。ただし、切り刻まれたメソッドの実行方法は、await演算子に渡される式の種類によって異なります。

ほとんどの場合、await型の式にTaskを使用します。 Taskのawaitパターンの実装は、それがSynchronizationContextに従うという点で「スマート」です。これは基本的に次のことを引き起こします。

  1. awaitに入るスレッドがDispatcherまたはWinFormsメッセージループスレッド上にある場合、asyncメソッドのチャンクはメッセージキューの処理の一部として発生します。
  2. awaitに入るスレッドがスレッドプールスレッドにある場合、asyncメソッドの残りの部分はスレッドプールのどこかに発生します。

それがおそらく問題に遭遇している理由です - 非同期メソッドの実装はDispatcherで残りを実行しようとしています - それが中断されていても。

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

私は質問をしなければなりません、why​​ _あなたは非同期メソッドを同期的にブロックしようとしていますか?そうすることは、なぜそのメソッドが非同期的に呼び出されることを望んでいたのかという目的を無効にするでしょう。一般的に、DispatcherまたはUIメソッドでawaitを使い始めるときは、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にインストールします。それはあなたの問題に対する一つの回避策です。

別の回避策は、スレッドプールスレッドで非同期メソッドを実行し、それが完了するのを待つことです。そうすることは簡単です - あなたは次の断片でそれをすることができます:

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

最後のAPIはTask.Run(...)ですが、CTPではEx接尾辞が必要です( 説明はここ )。

21
Theo Yaung

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

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

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

18
pixel

私はそれを何度か直面しました、大体ユニットテストやウィンドウズサービス開発で。現在私はいつもこの機能を使っています:

        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");

それは簡単で、簡単で、私は何の問題もありませんでした。

16
J. Lennon

私は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
wenhx

ちょっと注意してください - このアプローチ:

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のコメントによると)

11
RredCat

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

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

編集:

あなたはあなたが例外を受けると言います。スタックトレースを含む詳細を投稿してください。
Mono contains 次のテストケース

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

        Assert.AreEqual (1, val);
}

これがあなたのために働くかどうか確認してください。そうでなければ、非常にありそうもないが、Async CTPの奇妙なビルドがあるかもしれません。これでうまくいく場合は、コンパイラが正確に生成するものと、Taskインスタンス化がこのサンプルとはどのように異なるのかを検討する必要があります。

編集#2:

m_actionnullのときに説明した例外が発生することをReflectorに確認しました。これはちょっと変ですが、Async CTPの専門家ではありません。私が言ったように、あなたはあなたのコードを逆コンパイルし、Taskがどのように正確にインスタンス化されているかを見る必要がありますm_actionnullです。


P.S時折の投票に対する対処はどうですか?詳しく説明しますか?

9
Dan Abramov

次のような電話をかけないでください。

Service.GetCustomers();

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

7
Daniel A. White

以下のコードスニップを使用

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

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

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

この拡張メソッドは、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
Contango

.Result;または.Wait()を呼び出すだけで、多くの人がコメントで言っているようにデッドロックの危険があります。私たちのほとんどは一人ぼっちが好きなので、あなたは.Net 4.5<のためにこれらを使うことができます

非同期メソッドで値を取得する:

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

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

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

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

ソース:

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

3
Ogglas

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

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

.NET 4.6でテスト済み。デッドロックも回避できます。

Aysncメソッドの場合はTaskを返します。

Task DoSomeWork()

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

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

Task<T> GetSomeValue()

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

編集

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

1
Liang

私は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()を使う必要はありません。また、タスクが完了しない場合にタイムアウトを指定して、永遠に動けなくなることもありません。

0
Curtis