c# - 博客园 - configureawait用法




我将如何同步运行异步任务<T>方法? (15)

我正在学习异步/等待,并遇到了需要同步调用异步方法的情况。 我怎样才能做到这一点?

异步方法:

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

我正在学习异步/等待,并遇到了需要同步调用异步方法的情况。 我怎样才能做到这一点?

最好的答案是你没有 ,细节取决于“情况”是什么。

它是一个属性getter / setter吗? 在大多数情况下,拥有异步方法比“异步属性”更好。 (欲了解更多信息,请参阅我的博客文章异步属性 )。

这是一个MVVM应用程序,你想做异步数据绑定? 然后使用类似于我的NotifyTask东西,如我的MSDN文章中关于异步数据绑定所述

它是一个构造函数吗? 那么你可能想考虑一个异步工厂方法。 (欲了解更多信息,请参阅我的博客文章异步构造函数 )。

几乎总是有一个比同步异步更好的答案。

如果你的情况不可能(你知道这是通过在这里描述一个问题来描述这种情况 ),那么我建议使用同步代码。 异步一路是最好的; 一路同步是第二好的。 不推荐使用Sync-over-async。

但是,有一些情况需要sync-over-async。 具体来说,您受到调用代码的限制,因此必须进行同步(并且绝对没有办法重新考虑或重新构造代码以允许异步), 并且 必须调用异步代码。 这是一种非常罕见的情况,但它不时出现。

在这种情况下,您需要使用我在关于brownfield async开发的文章中描述的其中一种攻击,具体为:

  • 阻止(例如, GetAwaiter().GetResult() )。 请注意, 这可能会导致死锁 (正如我在我的博客中所描述的)。
  • 在线程池线程上运行代码(例如, Task.Run(..).GetAwaiter().GetResult() )。 请注意,只有异步代码可以在线程池线程上运行(即,不依赖于UI或ASP.NET上下文),这才会起作用。
  • 嵌套消息循环。 请注意,如果异步代码只假定单线程上下文,而不是特定的上下文类型(很多UI和ASP.NET代码需要特定的上下文),则这只会起作用。

嵌套消息循环是所有黑客中最危险的,因为它会导致re-entrancy 。 重入是非常棘手的原因,并且(IMO)是Windows上大多数应用程序错误的原因。 特别是,如果你在UI线程中并且阻塞了工作队列(等待异步工作完成),那么CLR实际上会为你提供一些消息 - 它实际上会处理来自你内部的一些Win32消息代码 。 哦,你不知道哪些信息 - 克里斯布鲁姆 说:“确切地知道会被抽到什么不是很好吗?不幸的是,抽水是一种超出凡人理解的黑色艺术。” ,那么我们真的没有希望知道。

所以,当你在UI线程上这样阻塞时,你就会遇到麻烦。 另一篇来自同一篇文章的cbrumme引用:“有时候,公司内部或外部的客户发现我们在STA [UI线程]上的管理阻塞期间抽出消息。这是一个合理的问题,因为他们知道这非常困难编写可重入的代码。“

是的。 很难编写在重入的情况下很强大的代码。 嵌套的消息循环迫使你编写在可重入的情况下可靠的代码。 这就是为什么对这个问题的接受(和最高评价)的答案在实践中是非常危险的。

如果你完全没有其他选择 - 你不能重新设计你的代码,你不能重新构造它是异步的 - 你被不可更改的调用代码强制为同步 - 你不能将下游代码更改为同步- 你不能阻止 - 你不能在一个单独的线程上运行异步代码 - 那么只有当你考虑拥抱重入时才会这样做。

如果你发现自己处在这个角落,我会推荐使用类似Dispatcher.PushFrame的WPF应用程序 ,循环与Application.DoEvents for WinForm应用程序,以及一般情况下,我自己的AsyncContext.Run


被告知这个答案是三岁。 我主要基于.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();

In your code, your first wait for task to execute but you haven't started it so it waits indefinitely. 尝试这个:

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

编辑:

You say that you get an exception. Please post more details, including stack trace.
Mono contains the following test case:

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

        Assert.AreEqual (1, val);
}

Check if this works for you. If it does not, though very unlikely, you might have some odd build of Async CTP. If it does work, you might want to examine what exactly the compiler generates and how Task instantiation is different from this sample.

Edit #2:

I checked with Reflector that the exception you described occurs when m_action is null . This is kinda odd, but I'm no expert on Async CTP. As I said, you should decompile your code and see how exactly Task is being instantiated any how come its m_action is null .

PS What's the deal with the occasional downvotes? Care to elaborate?


On wp8:

Wrap it:

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

Call it:

GetCustomersSynchronously();

Simply calling .Result; or .Wait() is a risk for deadlocks as many have said in comments. Since most of us like oneliners you can use these for .Net 4.5<

Acquiring a value via an async method:

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

Syncronously calling an async method

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

No deadlock issues will occur due to the use of Task.Run .

资源:

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


This answer is designed for anyone who is using WPF for .NET 4.5.

If you attempt to execute Task.Run() on the GUI thread, then task.Wait() will hang indefinitely, if you do not have the async keyword in your function definition.

This extension method solves the problem by checking to see if we are on the GUI thread, and if so, running the task on the WPF dispatcher thread.

This class can act as the glue between the async/await world and the non-async/await world, in situations where it is unavoidable, such as MVVM properties or dependencies on other APIs that do not use 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();
        }
    }
}

Why not create a call like:

Service.GetCustomers();

that isn't async.



以下是我发现的解决方法,适用于所有情况(包括暂停调度程序)。 这不是我的代码,我仍然在努力完全理解它,但它确实有效。

它可以使用以下方式调用:

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

代码来自here

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

只是一点点 - 这种方法:

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

Moreover this approach works for Windows Store solutions only!

Note: This way isn't thread safe if you call your method inside of other async method (according to comments of @Servy)


如果我正在阅读你的问题 - 需要同步调用异步方法的代码正在挂起的调度程序线程上执行。 而且你希望实际上同步阻塞该线程,直到异步方法完成。

C#5中的异步方法通过有效地将方法分解成各个部分,并返回一个Task来跟踪整个shabang的完成。 但是,chopped方法如何执行取决于传递给await运算符的表达式的类型。

大多数情况下,您将在Task类型的表达式上使用await 。 任务对await模式的实现是“聪明的”,因为它遵循SynchronizationContext ,这基本上导致发生以下情况:

  1. 如果进入await的线程位于Dispatcher或WinForms消息循环线程中,它将确保异步方法的块作为消息队列处理的一部分发生。
  2. 如果进入await的线程位于线程池线程上,则异步方法的剩余块将发生在线程池的任何位置。

这就是为什么你可能会遇到问题 - 异步方法实现正试图在Dispatcher上运行其余部分 - 即使它已被暂停。

.... 备份! ....

我必须问这个问题,你为什么试图同步阻塞异步方法? 这样做会打破为什么该方法想被异步调用的目的。 一般来说,当您开始使用Dispatcher或UI方法的await时,您会希望将整个UI流程变为异步。 例如,如果您的调用堆栈如下所示:

  1. [返回页首] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI代码] .Plumbing() - WPF或WinForms代码
  6. [消息循环] - WPF或WinForms消息循环

然后,一旦代码已被转换为使用异步,您通常会结束

  1. WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI代码] .Plumbing() - WPF或WinForms代码
  6. [消息循环] - WPF或WinForms消息循环

实际上回答

上面的AsyncHelpers类实际上起作用,因为它的行为像嵌套的消息循环,但它将自己的并行机制安装到Dispatcher,而不是试图在Dispatcher本身上执行。 这是您的问题的一种解决方法。

另一个解决方法是在线程池线程上执行您的异步方法,然后等待它完成。 这样做很简单 - 您可以使用以下代码片段完成此操作:

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

最终的API将是Task.Run(...),但对于CTP,您将需要Ex后缀( 在此解释 )。


惊讶没有人提到这个:

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

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

不像其他一些方法那样漂亮,但它具有以下优点:

  • 它不会吞下异常(如Wait
  • 它不会包装在AggregateException抛出的任何异常(如Result
  • 适用于TaskTask<T>自己尝试一下!

此外,由于GetAwaiter是鸭式输入,因此它适用于从异步方法(如ConfiguredAwaitableYieldAwaitable )返回的任何对象,而不仅仅是任务。

编辑:请注意,这种方法(或使用.Result )可能会死锁,除非每次等待时确保添加.ConfigureAwait(false) ,对于可能从BlahAsync()获得的所有异步方法BlahAsync() (不只是它直接调用的那些)。 Explanation

// 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()

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

我经历过几次,主要是单元测试或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");

这很简单,容易,我没有任何问题。


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