あなたの質問がGCに関するものであることは知っていますが、最初に非同期実装について話し、次に同じ問題が引き続き発生するかどうかを確認したいと思います。
最初の実装のサンプルコードから離れると、今はI/Oを待っている3つのCPUスレッドを無駄にすることになります。
- 無駄になる最初のスレッドは、呼び出しを実行している元のWCF I/Oスレッドです。子タスクがまだ未処理である間は、Task.WaitAllによってブロックされます。
- 無駄になっている他の2つのスレッドは、Service1とService2への呼び出しを実行するために使用しているスレッドプールスレッドです。
Service1とService2へのI/Oが未処理である間は、無駄にしている3つのCPUスレッドを使用して他の作業を実行することはできず、GCはそれらを回避する必要があります。
したがって、最初に推奨するのは、WCFメソッド自体を変更して、WCFランタイムでサポートされている非同期プログラミングモデル(APM)パターンを使用することです。これにより、サービス実装を呼び出す元のWCF I / Oスレッドをすぐにプールに戻して、他の着信要求を処理できるようにすることで、最初の無駄なスレッドの問題が解決されます。それが済んだら、次に、クライアントの観点からService1とService2への呼び出しも非同期にします。これには、次の2つのいずれかが含まれます。
- コントラクトインターフェイスの非同期バージョンを生成します。これも、WCFがクライアントモデルでもサポートするAPM BeginXXX/EndXXXを使用します。
- これらが話している単純なRESTサービスである場合、他に次の非同期の選択肢があります。
WebClient::DownloadStringAsync
実装(WebClient
個人的には私のお気に入りのAPIではありません)
HttpWebRequest::BeginGetResponse
+ HttpWebResponse::BeginGetResponseStream
+HttpWebRequest::BeginRead
- 新しいWebAPIで最先端を行く
HttpClient
これらすべてをまとめると、サービスでService1とService2からの応答を待っている間、無駄なスレッドは発生しません。WCFクライアントルートを使用した場合、コードは次のようになります。
// Represents a common contract that you talk to your remote instances through
[ServiceContract]
public interface IRemoteService
{
[OperationContract(AsyncPattern=true)]
public IAsyncResult BeginRunQuery(string query, AsyncCallback asyncCallback, object asyncState);
public string EndRunQuery(IAsyncResult asyncResult);
}
// Represents your service's contract to others
[ServiceContract]
public interface IMyService
{
[OperationContract(AsyncPattern=true)]
public IAsyncResult BeginMyMethod(string someParam, AsyncCallback asyncCallback, object asyncState);
public string EndMyMethod(IAsyncResult asyncResult);
}
// This would be your service implementation
public MyService : IMyService
{
public IAsyncResult BeginMyMethod(string someParam, AsyncCallback asyncCallback, object asyncState)
{
// ... get your service instances from somewhere ...
IRemoteService service1 = ...;
IRemoteService service2 = ...;
// ... build up your query ...
string query = ...;
Task<string> service1RunQueryTask = Task<string>.Factory.FromAsync(
service1.BeginRunQuery,
service1.EndRunQuery,
query,
null);
// NOTE: obviously if you are really doing exactly this kind of thing I would refactor this code to not be redundant
Task<string> service2RunQueryTask = Task<string>.Factory.FromAsync(
service2.BeginRunQuery,
service2.EndRunQuery,
query,
null);
// Need to use a TCS here to retain the async state when working with the APM pattern
// and using a continuation based workflow in TPL as ContinueWith
// doesn't allow propagation of async state
TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>(asyncState);
// Now we need to wait for both calls to complete before we process the results
Task aggregateResultsTask = Task.ContinueWhenAll(
new [] { service1RunQueryTask, service2RunQueryTask })
runQueryAntecedents =>
{
// ... handle exceptions, combine results, yadda yadda ...
try
{
string finalResult = ...;
// Propagate the result to the TCS
taskCompletionSoruce.SetResult(finalResult);
}
catch(Exception exception)
{
// Propagate the exception to the TCS
// NOTE: there are many ways to handle exceptions in antecedent tasks that may be better than this, just keeping it simple for sample purposes
taskCompletionSource.SetException(exception);
}
});
// Need to play nice with the APM pattern of WCF and tell it when we're done
if(asyncCallback != null)
{
taskCompletionSource.Task.ContinueWith(t => asyncCallback(t));
}
// Return the task continuation source task to WCF runtime as the IAsyncResult it will work with and ultimately pass back to use in our EndMyMethod
return taskCompletionSource.Task;
}
public string EndMyMethod(IAsyncResult asyncResult)
{
// Cast back to our Task<string> and propagate the result or any exceptions that might have occurred
return ((Task<string>)asyncResult).Result;
}
}
すべてが整ったら、Service1とService2のI / Oが未処理である間、技術的にはCPUスレッドは実行されません。これを行う際に、GCがほとんどの時間中断することを心配する必要さえあるスレッドはありません。現在実際にCPU作業が行われるのは、作業の元のスケジュールと、例外を処理して結果をマッサージするContinueWhenAllでの継続のみです。