我应该如何使用不允许异步控制器操作的 CMS 处理异步 API 调用?

How should I handle async API calls using a CMS that does not allow async controller actions?

我一直在使用 async/await 弄湿我的脚,我一直在阅读的大部分内容都是 "Don't Block on Async Code",除了 main 函数控制台应用程序。我发现自己无法遵循这个简单的规则:(

我的应用程序严重依赖于 WebAPI 层。我在 MVC 应用程序中使用的客户端使用 System.Net.Http.HttpClient 上的异步方法来调用 API 方法。目前我有两种处理方式。在某些地方我们在调用客户端后立即得到结果:

//WebAPI Client
public object GetSomething(int id)
{
    var task = _client_GetSomethingFromApiAsync(id);
    var result = task.Result;
    return result;
}

在其他人中,一直使用异步直到控制器。我正在使用 SiteFinity 并且不能使用异步控制器操作所以我最终得到这样的结果:

//WebAPI Client
public Task<object> GetSomethingAsync(int id)
{
    return _client_GetSomethingFromApiAsync(id);    
}

//Domain Service
public Task<object> GetSomethingDomainServiceAsync(int id)
{
    return _service.GetSomethingAsync(id);  
}

//Controller
public JsonResult GetSomethingControllerAction(int id)
{
    var result = _domainService.GetSomethingDomainServiceAsync(viewModel.id).Result;
    return Json(new { Success = true, Payload = result });
}

根据我的研究,这两种解决方案都不是理想的,尽管我没有遇到任何死锁问题并且一切似乎都在工作。有人可以帮助我理解我应该遵循的最佳模式并解释原因吗?

谢谢!

据我了解,异步控制器操作只会为您节省一个线程。它对网站的行为没有其他影响。

所以如果没有异步控制器,你将不得不等到任务完成(阻塞线程,所以它不能用于另一个请求)。

或者您可以 return 在任务完成之前,然后每隔几秒继续轮询询问 "are we there yet?",这似乎相当极端,除非绝对必要,否则我不会这样做。

I am using SiteFinity and cannot use async controller actions

According to SiteFinity, it does support async controller actions, just not async actions in widgets. If this is your situation, I encourage you to vote.

假设您处于这种情况 - 并且不能使用 async 代码 - 那么我建议您一直使用 synchronous 代码。也就是说,将 HttpClient 替换为 WebClient 或类似的东西。我不建议使用 Result,因为即使您的代码今天没有死锁,将来对 HttpClient 的更新也有可能(不太可能,但有可能)导致死锁。

如果您不想失去在 async 中所做的投资,那么我建议您使用 "boolean argument hack",如我在 brownfield async 上的文章中所述.

您永远不应该只在异步方法上调用 Result,因为这可能会导致应用程序出现死锁。我不使用 SiteFinity,但在 MVC 中大量使用子操作,并且这些操作也不允许异步响应。我的做法是使用我从 Entity Framework 代码库中窃取的助手 class。

Entity Framework 6 除了原来的同步方法之外还添加了异步方法,但是在幕后,只有异步方法在做任何实际工作。同步方法现在只是同步调用异步方法。为此,他们实现了一个名为 AsyncHelper 的内部助手 class(内部可见性,因此您不能只在自己的应用程序中使用它)。但是,我获取了代码并在我的应用程序中创建了自己的 AsyncHelper

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

要在您的控制器操作中使用它,您只需执行以下操作:

var something = AsyncHelper.RunSync<SomeType>(() => _client_GetSomethingFromApiAsync(id));