##前提
・VS2019
・Windows Forms で実行していると仮定
##質問したい事
ライブラリの一部に、戻り値にTaskを持ったメソッドを作ろうとしています。
どのように非同期メソッドを作るのが正しいのかが知りたいです。
##悩んでいる事
ライブラリ内のメソッドで、Task.Run を使ってタスクを開始してもよいものなのでしょうか。
気にしている事として、幾つかのサイトをみてみた所Task.Run(or start, Task.FactoryStart)と書いてタスクを開始するのはユーザーが決めることでありライブラリが決めることではない、という文言が書いてありライブラリが勝手にスレッドプールを埋めてしまわないように配慮するのは確かにその通りなのかなと感じました。
https://www.infoq.com/jp/articles/Async-API-Design/
ただ、ライブラリのメソッド内でTask.Runを書かないという制約を守りつつ非同期メソッドを作るにはどうするのが正しい書き方なのかがわかりません。
##具体的な例
非常に簡易的ですが、以下のような非同期メソッドをライブラリに組み込もうとするとします。
C#
1public static Task<object> HogeAsync() 2{ 3 var tcs = new TaskCompletionSource<object>(); 4 new Task<object>(() => 5 { 6 //something 7 return "hoge"; 8 }).ContinueWith(t => 9 { 10 try 11 { 12 if (t.IsFaulted) 13 { 14 tcs.TrySetException(t.Exception); 15 } 16 else if (t.IsCanceled) 17 { 18 tcs.TrySetCanceled(); 19 } 20 else 21 { 22 tcs.TrySetResult(t.Result); 23 } 24 } 25 catch (Exception e) 26 { 27 tcs.TrySetException(e); 28 } 29 }); 30 31 return tcs.Task; 32}
これを呼び出す際には、以下の様になります。
C#
1var hoge = await HogeAsync();
しかし、これを Windows Forms 上で実行するととトークンをキャンセルするまで。処理がどこかへ行ってしまいます。
処理がどこかへいってしまう理由として、Taskが開始されていない状態でTaskを実行しようとしてしまっているせいだと考えています。なので先ほどのメソッドを以下の様に書き換えれば思った通りの挙動をしてくれます。
C#
1public static Task<object> HogeAsync() 2{ 3 var tcs = new TaskCompletionSource<object>(); 4 //この部分をTask.Runにしてタスクを開始する 5 Task.Run<object>(() =>
しかしこうすると、ライブラリのメソッド内でTask.Runを書かないという制約を守れなくなります。
一体、どうすれば…。
##ほかに調べた事
ネイティブなメソッドでは、なぜawaitキーワードで上手く待てるのかを調査してみました。
オーソドックスに、HttpClientクラスのGetAsyncメソッドを追っていってみたところ、Task.Runが使われている箇所がありました。使ってもよい場面があるのでしょうか。。
C#
1try 2{ 3 HttpWebRequest prepareWebRequest = this.CreateAndPrepareWebRequest(request); 4 state.webRequest = prepareWebRequest; 5 cancellationToken.Register(HttpClientHandler.s_onCancel, (object) prepareWebRequest); 6 if (ExecutionContext.IsFlowSuppressed()) 7 { 8 IWebProxy webProxy = (IWebProxy) null; 9 if (this._useProxy) 10 webProxy = this._proxy ?? WebRequest.DefaultWebProxy; 11 if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null) 12 this.SafeCaptureIdenity(state); 13 } 14 //ここでTask.Runつかってるじゃん 15 Task.Run((Action) (() => this._startRequest((object) state))); 16} 17catch (Exception ex) 18{ 19 this.HandleAsyncException(state, ex); 20}
回答3件
あなたの回答
tips
プレビュー