我有一个公共异步无效Foo()方法,我想从同步方法调用。到目前为止,我从MSDN文档中看到的都是通过异步方法调用异步方法,但我的整个程序并不是用异步方法构建的。

这可能吗?

下面是一个从异步方法调用这些方法的例子: 演练:使用Async和Await访问Web (c#和Visual Basic)

现在我正在研究从sync方法调用这些async方法。


当前回答

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

对于c# < 7.2,正确的方法是:

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


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

你会在很多微软文档中看到这个用法,例如: https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use-topics-subscriptions

其他回答

您可以从同步代码中调用任何异步方法,直到您需要等待它们,在这种情况下,它们也必须被标记为异步。

正如很多人在这里建议的那样,您可以在同步方法中对结果任务调用Wait()或Result,但最终会在该方法中进行阻塞调用,这在某种程度上违背了异步的目的。

如果你真的不能让你的方法异步,你不想锁定同步方法,那么你将不得不使用一个回调方法,把它作为参数传递给任务上的ContinueWith()方法。

我不是100%确定,但我相信这篇博客中描述的技巧在许多情况下都适用:

因此,如果想直接调用这个传播逻辑,可以使用task.GetAwaiter(). getresult()。

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

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()中的签名是不正确的。它应该是公共async Task Foo(),因为对于不返回值的异步方法,应该返回Task而不是void(是的,有一些罕见的例外)。

微软构建了一个AsyncHelper(内部)类来作为同步运行Async。源代码如下所示:

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

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

对于那些关心代码许可条款的人,这里有一个非常相似的代码链接(只是在线程上增加了对文化的支持),其中有注释表明它是由微软授权的MIT。https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs

这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).Result?说真的,微软现在不鼓励调用TaskFactory。StartNew,因为它们都是等效的,一个比另一个更可读。

绝对不是。

答案很简单

.Unwrap().GetAwaiter().GetResult() != .Result

首先

是任务。结果与.GetAwaiter.GetResult()?

其次,unwrap()会导致Task的设置不阻塞被包装的任务。

谁会问这个问题呢

这与调用Task不一样吗?运行(async ()=> await AsyncFunc()).GetAwaiter().GetResult()

那就得看情况了。

关于Task.Start(), Task.Run()和Task.Factory.StartNew()的使用

摘录:

的任务。Run使用taskcreateoptions。DenyChildAttach意思是子任务不能附加到父任务它使用TaskScheduler。默认值,这意味着在线程池中运行任务的线程将始终用于运行任务。 startnew使用TaskScheduler。Current表示当前线程的调度程序,它可能是TaskScheduler。违约但并非总是如此。

更多阅读:

指定同步上下文

ASP。NET Core SynchronizationContext

为了更加安全,像这样调用AsyncHelper不是更好吗?RunSync(async () => await AsyncMethod().ConfigureAwait(false));通过这种方式,我们告诉"内部"方法"请不要尝试同步到上层上下文并取消锁"

非常好的观点,就像大多数对象架构问题一样,这要视情况而定。

作为一个扩展方法,你想强迫绝对每个调用,还是让程序员使用该函数配置他们自己的异步调用?我可以看到call 3场景的用例;它很可能不是你在WPF中想要的,当然在大多数情况下是有意义的,但考虑到在ASP中没有上下文。Net Core如果你能保证它是ASP内部的。Net Core,那就无所谓了。

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方法。最大的区别是逻辑流没有中断,这使得读写更容易。