Dor*_*oby 9 .net garbage-collection task-parallel-library
我有一个WCF服务.在服务工作期间,它需要调用两个Web服务.所以有类似的代码:
var task1 = Task.Factory.StartNew(() => _service1.Run(query));
var task2 = Task.Factory.StartNew(() => _service2.Run(query));
Task.WaitAll(new[] { task1 , task2 });
Run Code Online (Sandbox Code Playgroud)
大部分时间这种方法都可以,但偶尔我会看到执行时间出现峰值,第一项任务需要几秒钟才能开始.看着perfmon,我意识到这正是GC发生的时候.显然,GC优先于运行我的任务.这是不可接受的,因为延迟对我来说非常重要,我更喜欢GC在请求之间而不是在请求中间发生.
我尝试以不同的方式进行此操作,而不是旋转我自己的任务,我使用了WebClient.DownloadStringTask.
return webClient.DownloadStringTask(urlWithParmeters).ContinueWith(t => ProcessResponse(clientQuery, t.Result),
TaskContinuationOptions.ExecuteSynchronously);
Run Code Online (Sandbox Code Playgroud)
这没有用; GC现在在任务开始后运行,但在继续之前运行.再说一次,我猜它认为系统现在处于空闲状态,所以现在是开始GC的好时机.只是,我负担不起延迟.
使用TaskCreationOptions.LongRunning,导致调度程序使用非线程池线程,似乎解决了这个问题,但我不想创建这么多新线程 - 这个代码将运行很多(每个请求多次).
克服这个问题的最佳方法是什么?
我知道您的问题是关于 GC 的,但我想首先讨论异步实现,然后看看您是否仍然会遇到同样的问题。
离开最初实现的示例代码,您现在将浪费三个 CPU 线程等待 I/O:
一直以来,当 Service1 和 Service2 的 I/O 未完成时,您浪费的三个 CPU 线程无法用于执行其他工作,GC 必须小心翼翼地绕过它们。
因此,我最初的建议是更改 WCF 方法本身,以使用 WCF 运行时支持的异步编程模型 (APM) 模式。这解决了第一个浪费线程的问题,允许调用服务实现的原始 WCF I/O 线程立即返回到其池中,以便能够为其他传入请求提供服务。完成此操作后,接下来您还希望从客户端角度对 Service1 和 Service2 进行异步调用。这将涉及两件事之一:
WebClient::DownloadStringAsync实现(WebClient不是我个人最喜欢的 API)HttpWebRequest::BeginGetResponse+ HttpWebResponse::BeginGetResponseStream+HttpWebRequest::BeginReadHttpClient将所有这些放在一起,当您等待服务中的 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;
}
}
Run Code Online (Sandbox Code Playgroud)
一旦一切就绪,从技术上讲,当 Service1 和 Service2 的 I/O 未完成时,您将不会执行任何 CPU 线程。这样做时,GC 无需担心大多数时间会出现中断的线程。现在发生实际 CPU 工作的唯一时间是工作的原始调度,然后继续在ContinueWhenAll 上处理任何异常并处理结果。