问题描述
我产生了代理基于任务的操作。
应如何服务正确(处置的 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块中的模式。当持续发生时,它可以运行在不同的线程和OperationContextScope是线程特定的。如果您需要调用等待一个异步调用,使用它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);
}
}
我的问题与他的code,是他从来没有呼吁ServiceClient关闭(或终止)。
My problem with his code, is that he never calls Close (or Abort) on the ServiceClient.
我还发现传播 > OperationContextScope 使用自定义的的SynchronizationContext
。但是,除了事实,这是一个很大的风险code,他说:
值得一提的是,它确实有关于处置操作上下文范围(因为它们只允许你处理它们调用线程上的)一些小问题,但这似乎不是,因为一个问题(在至少根据拆卸),它们实现Dispose(),而不是最终确定()。
通过大量的来自用户Noseratio帮助下,我想出了一些作品:不使用 OperationContextScope
。如果你正在使用它的任何these原因,试图找到适合您的方案一种解决方法。否则,如果你真的,真的,需要 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).
所以,完整的工作code是:
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
,你不能只返回工作
你从代理(这应该很自然的,为什么要创建一个新的工作
时,你已经有一个)。那么,在这种情况下,你需要等待
代理工作
和然后的接近(或中止)的 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)!
OK,我们有办法让它工作,但它会是不错的得到答案从权威人士透露,作为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.
推荐答案
我觉得一个可行的解决方案可能是使用自定义awaiter 通过流动的新操作环境的OperationContext .Current
。该implementation对的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);
}
}
}
这篇关于使用异步调用WCF服务模式/待机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!