问题描述
我用 基于任务的操作.
应该如何使用 async/await 正确调用此服务(之后处理 ServiceClient
和 OperationContext
)?
How should this service be invoked properly (disposing of the ServiceClient
and the OperationContext
afterwards) using async/await?
我的第一次尝试是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
成为 ServiceHelper
一个创建 ServiceClient
和 OperationContextScope
并在之后处理它们的类:
Being ServiceHelper
a class which creates the ServiceClient
and the OperationContextScope
and disposes of them afterwards:
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 正在与创建的线程不同."
However, this failed miserably when calling two services at the same time with the following error: "This OperationContextScope is being disposed on a different thread than it was created."
MSDN 说:
不要在 OperationContextScope 块中使用异步await"模式.当延续发生时,它可能在不同的线程上运行,而 OperationContextScope 是线程特定的.如果您需要为异步调用调用await",请在 OperationContextScope 块之外使用它.
这就是问题所在!但是,我们如何正确修复它?
So that's the problem! But, how do we fix it properly?
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).
My problem with his code, is that he never calls Close (or Abort) on the ServiceClient.
我还发现了 一种使用自定义SynchronizationContext
传播OperationContextScope
的方法.但是,除了它有很多风险"的事实外,代码,他说:
I also found a way of propagating the OperationContextScope
using a custom SynchronizationContext
. But, besides the fact that it's a lot of "risky" code, he states that:
值得注意的是,它在处理操作上下文范围方面确实存在一些小问题(因为它们只允许您在调用线程上处理它们),但这似乎不是问题,因为(在至少根据反汇编),它们实现了 Dispose() 但没有实现 Finalize().
那么,我们在这里倒霉了吗?是否有使用 async/await 和处理 ServiceClient
和 OperationContextScope
来调用 WCF 服务的经过验证的模式?也许微软的某个人(也许是大师 Stephen Toub :))可以提供帮助.
So, are we out of luck here? Is there a proven pattern for calling WCF services using async/await AND disposing of BOTH the ServiceClient
and the OperationContextScope
? Maybe someone form Microsoft (perhaps guru Stephen Toub :)) can help.
谢谢!
[更新]
在用户 Noseratio 的大量帮助下,我想出了一些有效的方法:不要使用 OperationContextScope
.如果您将它用于任何这些 原因,尝试找到适合您场景的解决方法.否则,如果你真的、真的需要 OperationContextScope
,你将不得不想出一个捕获它的 SynchronizationContext
的实现,这看起来 非常难(如果可能的话 - 这不是默认行为一定是有原因的).
With a lot of help from user Noseratio, I came up with something that works: do not use OperationContextScope
. If you are using it for any of these reasons, try to find a workaround that fits your scenario. Otherwise, if you really, really, need OperationContextScope
, you'll have to come up with an implementation of a SynchronizationContext
that captures it, and that seems very hard (if at all possible - there must be a reason why this isn't the default behavior).
所以,完整的工作代码是:
So, the full working code is:
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;
}
}
}
}
注意该类支持扩展;也许您需要继承并提供凭据.
Note that the class supports extension; perhaps you need to inherit and provide credentials.
唯一可能的陷阱"是在 GetHomeInfoAsync
中,您不能只返回从代理获得的 Task
(这看起来很自然,为什么要创建一个新的 Task
当你已经有了一个).那么,在这种情况下,您需要 await
代理 Task
并然后关闭(或中止)ServiceClient
,否则,您将在调用服务后立即关闭它(同时通过网络发送字节)!
The only possible "gotcha" is that in GetHomeInfoAsync
, you can't just return the Task
you get from the proxy (which should seem natural, why create a new Task
when you already have one). Well, in this case you need to await
the proxy Task
and then close (or abort) the ServiceClient
, otherwise you'll be closing it right away after invoking the service (while bytes are being sent over the wire)!
好的,我们有办法让它发挥作用,但正如 Noseratio 所说,从权威来源获得答案会很好.
OK, we have a way to make it work, but it'd be nice to get an answer from an authoritative source, as Noseratio states.
推荐答案
我认为一个可行的解决方案可能是使用 自定义等待器 通过 OperationContext.Current.
OperationContext
本身的实现似乎不需要线程关联.这是模式:
I think a feasible solution might be to use a custom awaiter to flow the new operation context via OperationContext.Current
. The implementation of OperationContext
itself doesn't appear to require thread affinity. Here is the pattern:
async Task TestAsync()
{
using(var client = new WcfAPM.ServiceClient())
using (var scope = new FlowingOperationContextScope(client.InnerChannel))
{
await client.SomeMethodAsync(1).ContinueOnScope(scope);
await client.AnotherMethodAsync(2).ContinueOnScope(scope);
}
}
这是FlowingOperationContextScope
和ContinueOnScope
的实现(只是稍微测试了一下):
Here is the implementation of FlowingOperationContextScope
and ContinueOnScope
(only slightly tested):
public sealed class FlowingOperationContextScope : IDisposable
{
bool _inflight = false;
bool _disposed;
OperationContext _thisContext = null;
OperationContext _originalContext = null;
public FlowingOperationContextScope(IContextChannel channel):
this(new OperationContext(channel))
{
}
public FlowingOperationContextScope(OperationContext context)
{
_originalContext = OperationContext.Current;
OperationContext.Current = _thisContext = context;
}
public void Dispose()
{
if (!_disposed)
{
if (_inflight || OperationContext.Current != _thisContext)
throw new InvalidOperationException();
_disposed = true;
OperationContext.Current = _originalContext;
_thisContext = null;
_originalContext = null;
}
}
internal void BeforeAwait()
{
if (_inflight)
return;
_inflight = true;
// leave _thisContext as the current context
}
internal void AfterAwait()
{
if (!_inflight)
throw new InvalidOperationException();
_inflight = false;
// ignore the current context, restore _thisContext
OperationContext.Current = _thisContext;
}
}
// ContinueOnScope extension
public static class TaskExt
{
public static SimpleAwaiter<TResult> ContinueOnScope<TResult>(this Task<TResult> @this, FlowingOperationContextScope scope)
{
return new SimpleAwaiter<TResult>(@this, scope.BeforeAwait, scope.AfterAwait);
}
// awaiter
public class SimpleAwaiter<TResult> :
System.Runtime.CompilerServices.INotifyCompletion
{
readonly Task<TResult> _task;
readonly Action _beforeAwait;
readonly Action _afterAwait;
public SimpleAwaiter(Task<TResult> task, Action beforeAwait, Action afterAwait)
{
_task = task;
_beforeAwait = beforeAwait;
_afterAwait = afterAwait;
}
public SimpleAwaiter<TResult> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get
{
// don't do anything if the task completed synchronously
// (we're on the same thread)
if (_task.IsCompleted)
return true;
_beforeAwait();
return false;
}
}
public TResult GetResult()
{
return _task.Result;
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
_task.ContinueWith(task =>
{
_afterAwait();
continuation();
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Current);
}
}
}
这篇关于使用 async/await 调用 WCF 服务的模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!