我使用基于任务的操作生成了一个代理。
应该如何使用async / await 正确调用此服务(处置ServiceClient和OperationContext之后)?
ServiceClient
OperationContext
我的第一次尝试是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } }
作为ServiceHelper创建the ServiceClient和the OperationContextScope并随后对其进行处理的类:
ServiceHelper
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是特定于线程的。如果您需要为异步调用调用“ await”,请在OperationContextScope块之外使用它。
这就是问题所在!但是,我们如何正确修复它?
这个家伙正是MSDN所说的:
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(或中止)。
我还找到了一种OperationContextScope使用自定义进行传播的方法SynchronizationContext。但是,除了有很多“风险”代码这一事实之外,他还指出:
SynchronizationContext
值得注意的是,关于操作上下文作用域的处置确实存在一些小问题(因为它们仅允许您将它们处置在调用线程上),但这似乎不是问题,因为(至少根据反汇编),它们实现Dispose()而不是Finalize()。
那么,我们在这里不走运吗?是否存在使用async / await并同时处理the ServiceClient和the 来调用WCF服务的行之有效的模式OperationContextScope?也许来自Microsoft(也许是Stephen Toub大师)的人可以提供帮助。
谢谢!
[更新]
在Noseratio用户的大量帮助下,我想到了一个可行的方法:不要使用OperationContextScope。如果出于任何这些原因使用它,请尝试查找适合您情况的解决方法。否则,如果确实需要OperationContextScope,则必须提出一个SynchronizationContext捕获它的实现,这似乎很难(如果有可能,这一定是为什么这不是默认行为的原因) )。
因此,完整的工作代码是:
与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,否则调用服务后(在通过有线发送字节的同时)立即关闭它!
GetHomeInfoAsync
Task
await
好的,我们有办法使它起作用,但是如Noseratio所言,最好是从权威来源获得答案。
我认为可行的解决方案可能是使用 自定义等待程序 来通过 传递 新的操作上下文OperationContext.Current。本身的实现OperationContext似乎不需要线程关联。这是模式:
OperationContext.Current
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(仅略测试):
FlowingOperationContextScope
ContinueOnScope
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); } } }