使用C# - ASP.NET MVC 4,我可以定义一个异步控制器动作,如:
public async Task<ActionResult> IndexWorks()
{
var data = await DownloadAsync("http://stackoverflow.com");
return Content(data);
}
使用F#有没有办法做类似的事情?
我知道我可以使用AsyncManager
方法。我也知道@Tomas Petricek做了一个非常整洁的AsyncActionBuilder
,但与C#方法相比,它感觉就像很多样板。
答案 0 :(得分:7)
async / await使用Tasks,因此您需要在Task对象和F#Async对象之间来回转换。要从Task转换为Async,请使用Async.AwaitTask
。相反使用Async.StartAsTask
。你的例子变成了:
member x.IndexWorks() =
async {
let! data = Async.AwaitTask (DownloadAsync "http://stackoverflow.com")
return x.Content(data)
} |> Async.StartAsTask
或者,您可以使用适用于开箱即用的任务的计算表达式,而不是使用async
计算表达式。 FSharpx中有一个:
let task = FSharpx.Task.TaskBuilder()
(...)
member x.IndexWorks() = task {
let! data = DownloadAsync "http://stackoverflow.com"
return x.Content(data)
}
答案 1 :(得分:1)
我认为可能有很多人试图做像
这样的事情type SomeController() =
inherit ApiController()
member x.Get() =
let data = Download("http://stackoverflow.com")
x.Ok(data) :> IHttpActionResult // Using built in Ok, BadRequest, etc.
来自C#WebApi控制器的预期type Get() = unit -> Task<IHttpActionResult>
如果您尝试按照接受的答案提示(尝试使用内置的Ok
,BadRequest
等方法),则会遇到
无法从lambda中访问受保护的成员
为了解决这个问题,我直接使用ExtensionMethods,而不是尝试在MVC期待的async {}
和Task
之间进行扭曲
type SomeController() =
inherit ApiController()
member x.Get() = async {
let! data = DownloadAsync("http://stackoverflow.com") |> Async.AwaitTask
return System.Web.Http.Results.OkNegotiatedContentResult(data, x) :> IHttpActionResult // Pass in 'this' pointer (x) into extension method along with data
} |> Async.StartAsTask
使用额外的向上转发:> IHttpActionResult
,您还可以从模型中返回不同的行为BadRequest
等,并且仍然运行async
并且类型签名应该运行并且干净地编译< / p>
答案 2 :(得分:0)
AsyncWorkflowController
,可以从Async<ActionResult>
返回ActionResult
。可以在http://fssnip.net/5q找到AsyncWorkFlowController
的代码。
然而,他的实现使得调试非常困难,因为在自定义控制器中重新抛出时不会保留堆栈跟踪。因此,我做了一些改变,以实现这一目标:
member actionDesc.EndExecute(asyncResult) =
match endAsync'.Value(asyncResult) with
| Choice1Of2 value -> box value
| Choice2Of2 why ->
// Preserve the stack trace, when rethrow
ExceptionDispatchInfo.Capture(why).Throw()
obj() (* Satisfy return value *) } } }
此外,我更改了以下行:new ReflectedControllerDescriptor(controllerType)
,
到new ReflectedAsyncControllerDescriptor(controllerType)
- 然而,这种变化纯粹是可选的,因为它不会有任何区别。我发现使用Async
更合乎逻辑。
完整的代码将是:
open System
open System.Web.Mvc
open System.Web.Mvc.Async
open System.Runtime.ExceptionServices
open Unchecked
type AsyncWorkflowController() =
inherit AsyncController()
override __.CreateActionInvoker() =
upcast { new AsyncControllerActionInvoker() with
member __.GetControllerDescriptor(controllerContext) =
let controllerType = controllerContext.Controller.GetType()
upcast { new ReflectedAsyncControllerDescriptor(controllerType) with
member ctrlDesc.FindAction(controllerContext, actionName) =
let forwarder = base.FindAction(controllerContext, actionName) :?> ReflectedActionDescriptor
if(forwarder = null || forwarder.MethodInfo.ReturnType <> typeof<Async<ActionResult>>) then
upcast forwarder
else
let endAsync' = ref (defaultof<IAsyncResult -> Choice<ActionResult, exn>>)
upcast { new AsyncActionDescriptor() with
member actionDesc.ActionName = forwarder.ActionName
member actionDesc.ControllerDescriptor = upcast ctrlDesc
member actionDesc.GetParameters() = forwarder.GetParameters()
member actionDesc.BeginExecute(controllerContext, parameters, callback, state) =
let asyncWorkflow =
forwarder.Execute(controllerContext, parameters) :?> Async<ActionResult>
|> Async.Catch
let beginAsync, endAsync, _ = Async.AsBeginEnd(fun () -> asyncWorkflow)
endAsync' := endAsync
beginAsync((), callback, state)
member actionDesc.EndExecute(asyncResult) =
match endAsync'.Value(asyncResult) with
| Choice1Of2 value -> box value
| Choice2Of2 why ->
// Preserve the stack trace, when rethrow
ExceptionDispatchInfo.Capture(why).Throw()
obj() (* Satisfy return value *) } } }
用法:
type TestController() =
inherit AsyncWorkflowController()
member x.IndexWorks() = async {
let startThread = Thread.CurrentThread.ManagedThreadId
let! data = asyncDownload "http://stackoverflow.com"
let endThread = Thread.CurrentThread.ManagaedThreadId
return ContentResult(Content = "Start = %i | End = %i" startThread endThread) :> ActionResult }
要确认它确实执行了所有异步操作,并且没有阻止ASP.NET池中的任何线程,请使用:
member x.IndexWorks() = async {
let startThread = Thread.CurrentThread.ManagedThreadId
let! data = asyncDownload "http://stackoverflow.com"
let endThread = Thread.CurrentThread.ManagaedThreadId
return ContentResult(Content = "Start = %i | End = %i" startThread endThread) :> ActionResult }
开始和结束线程会有所不同,因此启动线程被放回到池中,并且在异步操作完成时返回了一个新线程。