C# 从非异步代码调用异步方法

C# 从非异步代码调用异步方法,c#,asp.net,multithreading,asynchronous,task-parallel-library,C#,Asp.net,Multithreading,Asynchronous,Task Parallel Library,我正在更新一个库,它有一个内置于.NET3.5中的API界面。因此,所有方法都是同步的。我无法更改API(即,将返回值转换为任务),因为这将要求所有调用者都更改。所以我只剩下如何以同步方式最好地调用异步方法。这在ASP.NET 4、ASP.NET Core和.NET/.NET Core控制台应用程序的上下文中 我可能还不够清楚-情况是我现有的代码不支持异步,我想使用新的库,如System.Net.Http和AWS SDK,它们只支持异步方法。因此,我需要弥合这一差距,并且能够拥有可以同步调用但可

我正在更新一个库,它有一个内置于.NET3.5中的API界面。因此,所有方法都是同步的。我无法更改API(即,将返回值转换为任务),因为这将要求所有调用者都更改。所以我只剩下如何以同步方式最好地调用异步方法。这在ASP.NET 4、ASP.NET Core和.NET/.NET Core控制台应用程序的上下文中

我可能还不够清楚-情况是我现有的代码不支持异步,我想使用新的库,如System.Net.Http和AWS SDK,它们只支持异步方法。因此,我需要弥合这一差距,并且能够拥有可以同步调用但可以在其他地方调用异步方法的代码

我已经读了很多书,并且有很多次这被问到和回答

问题是大多数答案都不一样!我见过的最常用的方法是使用.Result,但这可能会导致死锁。我已经尝试了下面的所有方法,它们都很有效,但我不确定哪种方法是避免死锁、具有良好性能和良好运行时性能的最佳方法(在尊重任务调度器、任务创建选项等方面)。有明确的答案吗?最好的方法是什么

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }
private static T taskSyncRunner(Func任务)
{
T结果;
//方法1
结果=Task.Run(异步()=>await Task()).ConfigureAwait(错误).GetAwaiter().GetResult();
//方法2
结果=Task.Run(Task).ConfigureAwait(false).GetAwaiter().GetResult();
//方法3
结果=任务().ConfigureAwait(false).GetAwaiter().GetResult();
//方法4
结果=任务。运行(任务)。结果;
//方法5
结果=Task.Run(Task.GetAwaiter().GetResult();
//方法6
var t=task();
t、 同步运行();
结果=t.结果;
//方法7
var t1=任务();
Task.WaitAll(t1);
结果=t1。结果;
//方法8?
返回结果;
}
所以我只剩下如何以同步方式最好地调用异步方法

首先,这是一件可以做的事情。我之所以这样说,是因为在堆栈溢出中,指出这是魔鬼的行为是一种笼统的陈述,而不考虑具体情况是很常见的

它不需要一直异步才能正确。阻止某个异步对象使其同步可能会带来性能成本,这可能很重要,也可能完全不相关。这取决于具体情况

死锁来自两个试图同时进入同一单线程同步上下文的线程。任何能够可靠地避免这种情况的技术都可以避免阻塞造成的死锁

在这里,您对
.ConfigureAwait(false)
的所有调用都是无意义的,因为您没有等待

RunSynchronously
无法使用,因为并非所有任务都可以通过这种方式处理

.GetAwaiter().GetResult()
不同于
Result/Wait()
,因为它模拟了
Wait
异常传播行为。你需要决定你是否想要。(因此,请研究这种行为是什么;无需在此重复。)

除此之外,所有这些方法都具有相似的性能。他们会以这样或那样的方式分配操作系统事件并阻止它。这是最昂贵的部分。我不知道哪种方法绝对是最便宜的

我个人喜欢
Task.Run(()=>DoSomethingAsync()).Wait()模式非常简单,因为它从分类上避免了死锁,并且不会隐藏一些
GetResult()
可能隐藏的异常。但是您也可以将
GetResult()
与此一起使用

我正在更新一个库,它有一个内置于.NET3.5中的API界面。因此,所有方法都是同步的。我无法更改API(即,将返回值转换为任务),因为这将要求所有调用者都更改。所以我只剩下如何以同步方式最好地调用异步方法

没有通用的“最佳”方式来执行异步反模式上的同步。只有各种各样的黑客都有自己的缺点

我建议您保留旧的同步API,然后在它们旁边引入异步API。您可以使用

首先,简要说明示例中每种方法的问题:

  • ConfigureAwait
    只有在存在
    await
    时才有意义;否则,它将一事无成
  • 结果
    将异常包装在一个
    聚合异常中
    ;如果必须阻止,请改用
    GetAwaiter().GetResult()
  • Task.Run
    将在线程池线程上执行其代码(显然)。只有当代码可以在线程池线程上运行时,这才是好的
  • RunSynchronously
    是一种高级API,在执行基于任务的动态并行时,在极少数情况下使用。你根本不在那种情况下
  • 带有单个任务的
    Task.WaitAll
    与仅
    Wait()
    相同
  • async()=>await x
    只是说
    ()=>x
    的一种效率较低的方式
  • 阻止从当前线程启动的任务
  • 以下是分类:

    // Problems (1), (3), (6)
    result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();
    
    // Problems (1), (3)
    result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();
    
    // Problems (1), (7)
    result = task().ConfigureAwait(false).GetAwaiter().GetResult();
    
    // Problems (2), (3)
    result = Task.Run(task).Result;
    
    // Problems (3)
    result = Task.Run(task).GetAwaiter().GetResult();
    
    // Problems (2), (4)
    var t = task();
    t.RunSynchronously();
    result = t.Result;
    
    // Problems (2), (5)
    var t1 = task();
    Task.WaitAll(t1);
    result = t1.Result;
    
    与这些方法中的任何一种不同的是,既然您已经有了可用的同步代码,那么您应该将其与较新的自然异步代码一起使用。例如,如果您的现有代码使用了
    WebClient

    public string Get()
    {
      using (var client = new WebClient())
        return client.DownloadString(...);
    }
    
    如果您想添加一个异步API,那么我会这样做:

    private async Task<string> GetCoreAsync(bool sync)
    {
      using (var client = new WebClient())
      {
        return sync ?
            client.DownloadString(...) :
            await client.DownloadStringTaskAsync(...);
      }
    }
    
    public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();
    
    public Task<string> GetAsync() => GetCoreAsync(sync: false);
    
    使用这种方法,您的逻辑将进入
    Core
    方法,这些方法可以同步或异步运行(由
    sync
    参数确定)。如果
    sync
    true
    ,则核心
    private string GetCoreSync()
    {
      using (var client = new WebClient())
        return client.DownloadString(...);
    }
    
    private static HttpClient HttpClient { get; } = ...;
    
    private async Task<string> GetCoreAsync(bool sync)
    {
      return sync ?
          GetCoreSync() :
          await HttpClient.GetString(...);
    }
    
    public string Get() => GetCoreAsync(sync: true).GetAwaiter().GetResult();
    
    public Task<string> GetAsync() => GetCoreAsync(sync: false);
    
         public ActionResult Test()
         {
            TestClass result = Task.Run(async () => await GetNumbers()).GetAwaiter().GetResult();
            return PartialView(result);
         }
    
        public async Task<TestClass> GetNumbers()
        {
            TestClass obj = new TestClass();
            HttpResponseMessage response = await APICallHelper.GetData(Functions.API_Call_Url.GetCommonNumbers);
            if (response.IsSuccessStatusCode)
            {
                var result = response.Content.ReadAsStringAsync().Result;
                obj = JsonConvert.DeserializeObject<TestClass>(result);
            }
            return obj;
        }