我知道你的问题是关于 GC 的,但我想先谈谈异步实现,然后看看你是否还会遇到同样的问题。
离开初始实现的示例代码,您现在将浪费三个 CPU 线程等待 I/O:
- 浪费的第一个线程是执行调用的原始 WCF I/O 线程。当子任务仍然未完成时,它将被 Task.WaitAll 阻止。
- 被浪费的另外两个线程是您用来执行对 Service1 和 Service2 的调用的线程池线程
一直以来,虽然 Service1 和 Service2 的 I/O 非常出色,但您浪费的三个 CPU 线程无法用于执行其他工作,并且 GC 必须在它们周围小心翼翼。
因此,我最初的建议是将您的 WCF 方法本身更改为使用 WCF 运行时支持的异步编程模型 (APM) 模式。这通过允许调用您的服务实现的原始 WCF I/O 线程立即返回其池以便能够为其他传入请求提供服务,从而解决了第一个浪费线程的问题。完成此操作后,您接下来要从客户端的角度对 Service1 和 Service2 进行异步调用。这将涉及以下两件事之一:
- 生成其合约接口的异步版本,同样使用 WCF 在客户端模型中支持的 APM BeginXXX/EndXXX。
- 如果这些是您正在与之交谈的简单 REST 服务,那么您还有以下其他异步选择:
WebClient::DownloadStringAsync
实现(WebClient
不是我个人最喜欢的 API)
HttpWebRequest::BeginGetResponse
+ HttpWebResponse::BeginGetResponseStream
+HttpWebRequest::BeginRead
- 使用新的 Web API 走在前沿
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 上处理任何异常并处理结果。