如何从C#中的同步方法调用异步方法?

时间:2012-02-18 17:49:28

标签: c# async-await

我想从同步方法调用public async void Foo()方法。到目前为止,我从MSDN文档中看到的是通过异步方法调用异步方法,但我的整个程序不是用异步方法构建的。

这甚至可能吗?

以下是从异步方法调用这些方法的一个示例:http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

现在我正在研究从同步方法中调用这些异步方法。

16 个答案:

答案 0 :(得分:550)

异步编程通过代码库“增长”。它一直是compared to a zombie virus。最好的解决方案是让它成长,但有时这是不可能的。

我在Nito.AsyncEx库中编写了一些类型来处理部分异步代码库。但是,没有适用于所有情况的解决方案。

解决方案A

如果您有一个不需要同步回其上下文的简单异步方法,那么您可以使用Task.WaitAndUnwrapException

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

想要使用Task.WaitTask.Result,因为它们会在AggregateException中包含例外。

此解决方案仅在MyAsyncMethod未同步回其上下文时才适用。换句话说,await中的每个MyAsyncMethod都应以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是可以的,因为RunTask会传播Task例外情况。)

您可能需要AsyncContext.RunTask而不是Task.WaitAndUnwrapException的原因是因为在WinForms / WPF / SL / ASP.NET上发生了相当微妙的死锁可能性:

  1. 同步方法调用异步方法,获取Task
  2. 同步方法对Task进行阻止等待。
  3. async方法使用await而不使用ConfigureAwait
  4. Task无法在这种情况下完成,因为它仅在async方法完成时完成; async方法无法完成,因为它正在尝试将其继续安排到SynchronizationContext,而WinForms / WPF / SL / ASP.NET将不允许继续运行,因为同步方法已在运行那个背景。
  5. 这就是为什么尽可能在每个ConfigureAwait(false)方法中使用async的好主意之一。

    解决方案C

    AsyncContext.RunTask在每种情况下都不起作用。例如,如果async方法等待需要UI事件完成的内容,那么即使使用嵌套上下文也会出现死锁。在这种情况下,您可以在线程池上启动async方法:

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

    但是,此解决方案需要MyAsyncMethod,它将在线程池上下文中起作用。因此它无法更新UI元素或访问ASP.NET请求上下文。在这种情况下,您也可以将ConfigureAwait(false)添加到其await语句中,并使用解决方案A.

    更新,2019-05-01:当前“最差最差做法”是MSDN article here

答案 1 :(得分:212)

添加一个最终解决了我的问题的解决方案,希望能节省一些时间。

首先阅读Stephen Cleary的几篇文章:

根据“不要阻止异步代码”中的“两个最佳实践”,第一个对我不起作用,第二个不适用(基本上如果我可以使用await,我做!)。

所以这是我的解决方法:将呼叫包裹在Task.Run<>(async () => await FunctionAsync());内,希望不再有死锁

这是我的代码:

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

答案 2 :(得分:174)

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

答案 3 :(得分:108)

async Main现在是C#7.2的一部分,可以在项目高级构建设置中启用。

对于C#&lt; 7.2,正确的方法是:

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


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

您会在许多Microsoft文档中看到这种情况,例如: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions

答案 4 :(得分:46)

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方法。逻辑流程的最大区别不在于中断,这使得写入和读取更加容易。

答案 5 :(得分:31)

我不是百分百肯定,但我相信this blog中描述的技术在许多情况下都适用:

  

如果要直接调用此传播逻辑,可以使用task.GetAwaiter().GetResult()

答案 6 :(得分:5)

对于任何再关注这个问题的人...

如果您查看Microsoft.VisualStudio.Services.WebApi,则有一个名为TaskExtensions的类。在该类中,您将看到静态扩展方法Task.SyncResult(),就像完全阻塞线程,直到任务返回一样。

内部调用task.GetAwaiter().GetResult()很简单,但是它可以重载返回asyncTaskTask<T>的任何Task<HttpResponseMessage>方法。语法糖,宝贝...爸爸的牙齿很甜。

看来...GetAwaiter().GetResult()是在阻塞上下文中执行异步代码的MS官方方法。对于我的用例来说似乎工作得很好。

答案 7 :(得分:3)

您可以使用同步代码调用任何异步方法,也就是说,直到需要await为止,在这种情况下,它们也必须标记为async

正如很多人在这里建议的那样,你可以在同步方法中调用结果任务上的Wait()或Result,但最后你会在该方法中调用阻塞调用,这会破坏异步的目的。

我真的无法使你的方法async并且你不想锁定同步方法,那么你将不得不通过将它作为参数传递给ContinueWith来使用回调方法任务方法。

答案 8 :(得分:2)

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

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

或使用此:

var result=result.GetAwaiter().GetResult().AccessToken

答案 9 :(得分:2)

我知道我来晚了。 但是,如果像我这样的人想要以一种整洁,简单的方式来解决这个问题,而又不必依赖其他库。

我从piece of code找到了以下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>());

答案 10 :(得分:2)

经过数小时尝试不同方法的尝试,或多或少都取得了成功,这就是我的结局。它不会在获取结果时陷入死锁,它还会获取并抛出原始异常,而不是包装的异常。

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

答案 11 :(得分:1)

受其他答案的启发,我创建了以下简单的辅助方法:

public static TResult RunSync<TResult>(Func<Task<TResult>> method)
{
    var task = method();
    return task.GetAwaiter().GetResult();
}

public static void RunSync(Func<Task> method)
{
    var task = method();
    task.GetAwaiter().GetResult();
}

可以按如下方式调用它们(取决于您是否返回值):

RunSync(() => Foo());
var result = RunSync(() => FooWithResult());

请注意,原始问题public async void Foo()中的签名不正确。应该为public async Task Foo(),因为对于不返回值的异步方法,您应该返回Task而不为空(是的,有一些罕见的例外)。

答案 12 :(得分:-1)

如果要运行“同步”

MethodAsync().RunSynchronously()

答案 13 :(得分:-1)

这是最简单的解决方案。我在互联网上的某个地方看到过它,我不记得在哪里,但是我一直在成功使用它。它不会死锁调用线程。

    void Synchronous Function()
    {
        Task.Run(Foo).Wait();
    }

    string SynchronousFunctionReturnsString()
    {
        return Task.Run(Foo).Result;
    }

    string SynchronousFunctionReturnsStringWithParam(int id)
    {
        return Task.Run(() => Foo(id)).Result;
    }

答案 14 :(得分:-3)

那些windows异步方法有一个叫做AsTask()的漂亮的小方法。您可以使用此方法让方法将自身作为任务返回,以便您可以手动调用Wait()。

例如,在Windows Phone 8 Silverlight应用程序中,您可以执行以下操作:

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

希望这有帮助!

答案 15 :(得分:-4)

   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;