我应该如何使用不允许异步控制器操作的 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));
我一直在使用 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));