5

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た .

4

3 に答える 3

1

実際、仲間のプログラマーであるドミトリー・モロゾフがそのようなことを可能にしたようです。からのAsyncWorkflowController復帰を可能にするカスタムを施した。のコードはhttp://fssnip.net/5q にありますAsync<ActionResult>ActionResultAsyncWorkFlowController

ただし、彼の実装では、カスタム コントローラーで再スローされたときにスタック トレースが保持されないため、デバッグが非常に困難になります。したがって、これを可能にするために少し変更を加えました。

 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),

to 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 }

開始スレッドと終了スレッドが異なるため、開始スレッドはプールに戻され、非同期操作が完了すると新しいスレッドが返されました。

于 2013-08-09T10:30:00.327 に答える