我使用基于任务的操作生成了一个代理。
应该如何使用 async/await正确调用此服务(处理ServiceClient
和之后的处理)?OperationContext
我的第一次尝试是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
作为ServiceHelper
一个在之后创建ServiceClient
和OperationContextScope
处理它们的类:
try
{
if (_operationContextScope != null)
{
_operationContextScope.Dispose();
}
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_operationContextScope = null;
_serviceClient = null;
}
但是,当同时调用两个服务并出现以下错误时,这会惨遭失败:“此 OperationContextScope 正在与创建时不同的线程上处理。”
MSDN说:
不要在 OperationContextScope 块中使用异步“等待”模式。当继续发生时,它可能在不同的线程上运行,并且 OperationContextScope 是特定于线程的。如果您需要为异步调用调用“等待”,请在 OperationContextScope 块之外使用它。
所以这就是问题所在!但是,我们如何正确修复它?
private async void DoStuffWithDoc(string docId)
{
var doc = await GetDocumentAsync(docId);
if (doc.YadaYada)
{
// more code here
}
}
public Task<Document> GetDocumentAsync(string docId)
{
var docClient = CreateDocumentServiceClient();
using (new OperationContextScope(docClient.InnerChannel))
{
return docClient.GetDocumentAsync(docId);
}
}
我对他的代码的问题是,他从不在 ServiceClient 上调用 Close(或 Abort)。
我还找到了一种OperationContextScope
使用自定义传播的方法SynchronizationContext
。但是,除了这是很多“有风险”的代码这一事实之外,他还指出:
值得注意的是,它在处理操作上下文范围方面确实存在一些小问题(因为它们只允许您在调用线程上处理它们),但这似乎不是问题,因为(至少根据反汇编),它们实现了 Dispose(),但没有实现 Finalize()。
那么,我们在这里不走运吗?是否有一种经过验证的模式可以使用 async/await 调用 WCF 服务并同时处理ServiceClient
和OperationContextScope
?也许微软的某个人(也许是大师 Stephen Toub :))可以提供帮助。
谢谢!
[更新]
在用户 Noseratio 的大力帮助下,我想出了一些可行的方法:不要使用OperationContextScope
. 如果您出于上述任何原因使用它,请尝试找到适合您的方案的解决方法。否则,如果你真的,真的,需要OperationContextScope
,你将不得不想出一个SynchronizationContext
捕获它的 a 的实现,这似乎非常困难(如果可能的话 - 一定有一个原因,为什么这不是默认行为)。
因此,完整的工作代码是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
存在ServiceHelper
:
public class ServiceHelper<TServiceClient, TService> : IDisposable
where TServiceClient : ClientBase<TService>, new()
where TService : class
{
protected bool _isInitialized;
protected TServiceClient _serviceClient;
public TServiceClient Proxy
{
get
{
if (!_isInitialized)
{
Initialize();
_isInitialized = true;
}
else if (_serviceClient == null)
{
throw new ObjectDisposedException("ServiceHelper");
}
return _serviceClient;
}
}
protected virtual void Initialize()
{
_serviceClient = new TServiceClient();
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
try
{
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_serviceClient = null;
}
}
}
}
请注意,该类支持扩展;也许您需要继承并提供凭据。
唯一可能的“陷阱”是在 中GetHomeInfoAsync
,您不能只返回Task
从代理获得的(这应该看起来很自然,为什么要Task
在已有代理的情况下创建一个新的)。好吧,在这种情况下,您需要await
代理Task
然后关闭(或中止) ,否则您将在调用服务后ServiceClient
立即关闭它(同时通过网络发送字节)!
好的,我们有办法让它工作,但最好从权威来源获得答案,正如 Noseratio 所说。