2013-08-08 4 views
4

Использование C# - ASP.NET MVC 4, я могу определить асинхронное действие контроллера, как:Действие Асинхронного контроллера в F #

public async Task<ActionResult> IndexWorks() 
{ 
    var data = await DownloadAsync("http://stackoverflow.com"); 
    return Content(data); 
} 

Есть ли способ сделать что-то подобное, используя F #?

Я знаю, что могу использовать подход AsyncManager. Я также знаю, что @ Томас Петричек сделал довольно аккуратный AsyncActionBuilder, но он просто чувствует себя как много шаблонов, по сравнению с подходом C#.

ответ

0

На самом деле это похоже на программиста, который сделал Морозов. Он сделал обычай AsyncWorkflowController, который позволяет вернуть Async<ActionResult> от ActionResult. Код для AsyncWorkFlowController можно найти по адресу http://fssnip.net/5q.

Однако его реализация очень затрудняет отладку из-за того, что трассировка стека не будет сохранена при повторном запуске в пользовательском контроллере. Поэтому я сделал небольшое изменение, чтобы сделать это возможным:

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 Pool, использование:

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 } 

Начальная и конечная нить будут отличаться, поэтому начальная нить была помещена обратно в poo l, а новое было возвращено, когда операция async завершилась.

7

async/await использует задачи, поэтому вам нужно будет преобразовать объекты Task и F # Async. Для преобразования из задачи в 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) 
} 
+0

по первому варианту, говорится, что он не может получить доступ к защищенным членам из лямбда – Maslow

1

Я думаю, что там может быть куча людей, которые пытаются сделать что-то вроде

type SomeController() = 
    inherit ApiController() 
    member x.Get() = 
     let data = Download("http://stackoverflow.com") 
     x.Ok(data) :> IHttpActionResult // Using built in Ok, BadRequest, etc. 

Где type Get() = unit -> Task<IHttpActionResult>, как и ожидалось от C# WebAPI контроллер

Если вы попытаетесь сделать это, как говорит принятый ответ (при попытке использовать встроенный Ok, BadRequest и т. д.методы) вы бежите в

не может получить доступ к защищенным членам внутри лямбда

Чтобы решить эту проблему, я использовал ExtensionMethods напрямую, а не пытаться сделать искажать между async {} и Task что MVC ожидает

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 и т.д. от модели и до сих пор это г un async и типом подписей должны быть разработаны и скомпилированы

Смежные вопросы