これは大まかな質問ではないかと思いますが、最近、C# から適切な F# に変換する方法を確認したいコードに出くわしました。旅はここ (1) (TPL-F# の相互作用に関する元の問題) から始まり、ここ(2) (F# に変換することを検討しているサンプル コード)から続きます。
サンプル コードは長すぎてここで再現できませんが、興味深い関数はActivateAsync
、RefreshHubs
およびAddHub
です。特に興味深い点は、
AddHub
の署名がありprivate async Task AddHub(string address)
ます。RefreshHubs
はループ内で を呼び出しAddHub
、 のリストを収集しますtasks
。これは最後に によって待機し、await Task.WhenAll(tasks)
その結果、戻り値は の署名と一致しますprivate async Task RefreshHubs(object _)
。RefreshHubs
ActivateAsync
asによって呼び出され、最後に関数 signature に一致await RefreshHubs(null)
する呼び出しがあります。await base.ActivateAsync()
public override async Task ActivateAsync()
質問:
インターフェイスと機能を維持し、既定のカスタム スケジューラを尊重する F# へのそのような関数シグネチャの正しい変換は何でしょうか? それ以外の点では、この「F# での async/await」についても確信が持てません。「機械的に」それを行う方法のように。:)
その理由は、リンク「here (1)」に問題があるように見えるためです (私はこれを確認していません)。F# 非同期操作は、(Orleans) ランタイムによって設定されたカスタムの協調スケジューラを尊重しません。また、ここでは、TPL 操作はスケジューラをエスケープしてタスク プールに移動するため、その使用は禁止されていると述べられています。
これに対処する方法の 1 つは、次のように F# 関数を使用することです。
//Sorry for the inconvenience of shorterned code, for context see the link "here (1)"...
override this.ActivateAsync() =
this.RegisterTimer(new Func<obj, Task>(this.FlushQueue), null, TimeSpan.FromMilliseconds(100.0), TimeSpan.FromMilliseconds(100.0)) |> ignore
if RoleEnvironment.IsAvailable then
this.RefreshHubs(null) |> Async.awaitPlainTask |> Async.RunSynchronously
else
this.AddHub("http://localhost:48777/") |> Async.awaitPlainTask |> Async.RunSynchronously
//Return value comes from here.
base.ActivateAsync()
member private this.RefreshHubs(_) =
//Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
//The return value is Task.
//In the C# version the AddHub provided tasks are collected and then the
//on the last line there is return await Task.WhenAll(newHubAdditionTasks)
newHubs |> Array.map(fun i -> this.AddHub(i)) |> Task.WhenAll
member private this.AddHub(address) =
//Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
//In the C# version:
//...
//hubs.Add(address, new Tuple<HubConnection, IHubProxy>(hubConnection, hub))
//}
//so this is "void" and could perhaps be Async<void> in F#...
//The return value is Task.
hubConnection.Start() |> Async.awaitTaskVoid |> Async.RunSynchronously
TaskDone.Done
このstartAsPlainTask
関数は、ここからSacha Barberによるものです。別の興味深いオプションは次のとおりです。
module Async =
let AwaitTaskVoid : (Task -> Async<unit>) =
Async.AwaitIAsyncResult >> Async.Ignore
<編集:Task.WhenAll
も待機する必要があることに気付きました。しかし、適切な方法は何でしょうか?ええと、寝る時間です (悪い駄洒落)...
<編集 2:ここ (1) (TPL-F# の相互作用に関する元の問題)では、Codeplex で、F# は同期コンテキストを使用して作業をスレッドにプッシュするが、TPL はそうではないことが言及されています。さて、これはもっともらしい説明だと思います (ただし、カスタム スケジューラに関係なく、これらのスニペットを適切に翻訳するにはまだ問題があります)。いくつかの興味深い追加情報がから得られる可能性があります
- SynchronizationContext を使用するタスクを取得するには? とにかく SynchronizationContext はどのように使用されますか?
- Await、SynchronizationContext、およびコンソール アプリで、
SingleThreadSynchronizationContext
実行される作業をキューに入れるように見える例が提供されています。これは使うべきなのではないでしょうか?
この文脈では、興味深い接線としてHopacに言及する必要があると思います。
<編集 3 : Danielとsvickは、カスタム タスク ビルダーを使用するようコメントで適切なアドバイスを提供しています。Daniel は、すでにFSharpxで定義されているものへのリンクを提供しています。
ソースを見ると、パラメーターを持つインターフェイスが次のように定義されていることがわかります
type TaskBuilder(?continuationOptions, ?scheduler, ?cancellationToken) =
let contOptions = defaultArg continuationOptions TaskContinuationOptions.None
let scheduler = defaultArg scheduler TaskScheduler.Default
let cancellationToken = defaultArg cancellationToken CancellationToken.None
オルレアンでこれを使用する場合は、ここのドキュメントTaskScheduler
に従っている必要があるようですTaskScheduler.Current
Orleans には独自のタスク スケジューラがあり、グレイン内で使用されるシングル スレッド実行モデルを提供します。タスクを実行するときは、.NET スレッド プールではなく、Orleans スケジューラを使用することが重要です。
グレイン コードでサブタスクを作成する必要がある場合は、Task.Factory.StartNew を使用する必要があります。
await Task.Factory.StartNew(() =>{ /* ロジック */ });
この手法では、現在のタスク スケジューラ (Orleans スケジューラ) を使用します。
常に .NET スレッド プールを使用する Task.Run の使用は避ける必要があるため、シングル スレッド実行モデルでは実行されません。
TaskScheduler.CurrentとTaskScheduler.Defaultの間に微妙な違いがあるようです。たぶん、これは、どの例のケースで望ましくない違いがあるかについての質問を正当化します. Orleansのドキュメントでは使用しないように指摘されておりTask.Run
、代わりに. . うーん、私が間違っていなければそうなるようです。Task.Factory.StartNew
.Default
.DenyAttachChilld
また、カスタム スケジューラに関してはTask.Run
vizに問題があるため、タスク スケジューラ (Task.Factory) で説明されているようにカスタムTaskFactoryを使用し、スレッドの数を制御することで、この特定の問題を解決できないかと考えています。同時実行を制限するタスク スケジューラ。Task.Factory.CreateNew
うーん、これはすでにかなり長い「熟考」になっています。どうやってこれを閉じればいいのだろうか?たぶん、svickとDanielがコメントを回答として作成でき、私が両方に賛成してsvickのを受け入れるとしたら?