C# 使用 async/await 调用 WCF 服务的模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18284998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pattern for calling WCF service using async/await
提问by gabrielmaldi
I generated a proxy with task-based operations.
我用基于任务的操作生成了一个代理。
How should this service be invoked properly (disposing of the ServiceClient
and the OperationContext
afterwards) using async/await?
应如何使用 async/await正确调用此服务(处理 theServiceClient
和 the OperationContext
after)?
My first attempt was:
我的第一次尝试是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
Being ServiceHelper
a class which creates the ServiceClient
and the OperationContextScope
and disposes of them afterwards:
作为ServiceHelper
一个创建 theServiceClient
和 theOperationContextScope
并在之后处理它们的类:
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;
}
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."
但是,当同时调用两个服务并出现以下错误时,此操作失败了:“此 OperationContextScope 正在与创建时不同的线程上处理。”
MSDNsays:
MSDN说:
Do not use the asynchronous “await” pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call “await” for an async call, use it outside of the OperationContextScope block.
不要在 OperationContextScope 块中使用异步“await”模式。当延续发生时,它可能在不同的线程上运行,而 OperationContextScope 是线程特定的。如果您需要为异步调用调用“await”,请在 OperationContextScope 块之外使用它。
So that's the problem! But, how do we fix it properly?
所以这就是问题所在!但是,我们如何正确修复它?
This guy did just what MSDN says:
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);
}
}
My problem with his code, is that he never calls Close (or Abort) on the ServiceClient.
我对他的代码的问题是他从不调用 ServiceClient 上的 Close(或 Abort)。
I also found a wayof propagating the OperationContextScope
using a custom SynchronizationContext
. But, besides the fact that it's a lot of "risky" code, he states that:
我还发现一个方式传播的OperationContextScope
使用自定义SynchronizationContext
。但是,除了它有很多“有风险”的代码这一事实之外,他还指出:
It's worth noting that it does have a few small issues regarding the disposal of operation-context scopes (since they only allow you to dispose them on the calling thread), but this doesn't seem to be an issue since (at least according to the disassembly), they implement Dispose() but not Finalize().
值得注意的是,它确实有一些关于操作上下文范围的处理的小问题(因为它们只允许您在调用线程上处理它们),但这似乎不是问题,因为(至少根据反汇编),它们实现 Dispose() 但不实现 Finalize()。
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.
那么,我们在这里倒霉了吗?是否有一种经过验证的模式可以使用 async/await 调用 WCF 服务并同时处理 theServiceClient
和 the OperationContextScope
?也许微软的某个人(也许是大师 Stephen Toub :))可以提供帮助。
Thanks!
谢谢!
[UPDATE]
[更新]
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 thesereasons, 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).
在用户 Noseratio 的大量帮助下,我想出了一些有效的方法:不要使用OperationContextScope
. 如果你正在使用它任何的这些理由,试图找到适合您的方案一种解决方法。否则,如果你真的真的需要OperationContextScope
,你将不得不想出一个SynchronizationContext
捕获它的实现,这似乎很难(如果可能的话 - 一定有一个原因为什么这不是默认行为)。
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);
}
}
With ServiceHelper
being:
随着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.
请注意,该类支持扩展;也许您需要继承并提供凭据。
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 thenclose (or abort) the ServiceClient
, otherwise you'll be closing it right away after invoking the service (while bytes are being sent over the wire)!
唯一可能的“问题”是,在GetHomeInfoAsync
, 你不能只返回Task
你从代理中得到的(这看起来很自然,为什么Task
在你已经有了一个新的时候再创建一个)。好吧,在这种情况下,您需要await
使用代理Task
,然后关闭(或中止)ServiceClient
,否则您将在调用服务后立即关闭它(同时通过网络发送字节)!
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.
好的,我们有办法让它发挥作用,但正如 Noseratio 所说,从权威来源获得答案会很好。
采纳答案by noseratio
I think a feasible solution might be to use a custom awaiterto flow the new operation context via OperationContext.Current
. The implementation of OperationContext
itself doesn't appear to require thread affinity. Here is the pattern:
我认为一个可行的解决方案可能是使用自定义等待程序通过OperationContext.Current
. 自身的实现OperationContext
似乎不需要线程关联。这是模式:
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);
}
}
Here is the implementation of FlowingOperationContextScope
and ContinueOnScope
(only slightly tested):
以下是FlowingOperationContextScope
and的实现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);
}
}
}
回答by Khoi Pham
I ran into the same issue, however it dawned on me that I didn't need to use async/await at all.
我遇到了同样的问题,但是我突然意识到我根本不需要使用 async/await。
Since you are not post processing the result, there is no need to wait for the reply. If you do need to process the result, just use the old fashion TPL continuation.
由于您没有对结果进行后期处理,因此无需等待回复。如果确实需要处理结果,只需使用旧式 TPL 延续。
public Task<MyDomainModel> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return helper.Proxy.GetHomeInfoAsync(timestamp).ContinueWith(antecedent=>processReplay(antecedent.Result));
}
}
回答by TMVector
I don't know if this helps, but after seeing this question on my search to answer the same question, I came upon this.
我不知道这是否有帮助,但是在我的搜索中看到这个问题来回答同样的问题后,我发现了这个。
Leading from that, I should think your code should look something like this:
从那开始,我应该认为你的代码应该是这样的:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var client = CreateDocumentServiceClient())
{
await client.BeginGetHomeInfoAsync(timestamp);
}
}
I realise my answer comes rather late :P but it might help someone else.
我意识到我的回答来得太晚了 :P 但它可能会帮助其他人。
回答by jamespconnor
I decide to write my own code that helps with this, posting in case this helps anyone. Seems to be a little less to go wrong (unforeseen races etc) vs the SimpleAwaiter implementation above but you be the judge:
我决定编写自己的代码来帮助解决这个问题,以防万一这对任何人都有帮助。与上面的 SimpleAwaiter 实现相比,错误(不可预见的比赛等)似乎少了一点,但你是判断者:
public static class WithOperationContextTaskExtensions
{
public static ContinueOnOperationContextAwaiter<TResult> WithOperationContext<TResult>(this Task<TResult> @this, bool configureAwait = true)
{
return new ContinueOnOperationContextAwaiter<TResult>(@this, configureAwait);
}
public static ContinueOnOperationContextAwaiter WithOperationContext(this Task @this, bool configureAwait = true)
{
return new ContinueOnOperationContextAwaiter(@this, configureAwait);
}
public class ContinueOnOperationContextAwaiter : INotifyCompletion
{
private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter;
private OperationContext _operationContext;
public ContinueOnOperationContextAwaiter(Task task, bool continueOnCapturedContext = true)
{
if (task == null) throw new ArgumentNullException("task");
_awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter();
}
public ContinueOnOperationContextAwaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return _awaiter.IsCompleted; } }
public void OnCompleted(Action continuation)
{
_operationContext = OperationContext.Current;
_awaiter.OnCompleted(continuation);
}
public void GetResult()
{
OperationContext.Current = _operationContext;
_awaiter.GetResult();
}
}
public class ContinueOnOperationContextAwaiter<TResult> : INotifyCompletion
{
private readonly ConfiguredTaskAwaitable<TResult>.ConfiguredTaskAwaiter _awaiter;
private OperationContext _operationContext;
public ContinueOnOperationContextAwaiter(Task<TResult> task, bool continueOnCapturedContext = true)
{
if (task == null) throw new ArgumentNullException("task");
_awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter();
}
public ContinueOnOperationContextAwaiter<TResult> GetAwaiter() { return this; }
public bool IsCompleted { get { return _awaiter.IsCompleted; } }
public void OnCompleted(Action continuation)
{
_operationContext = OperationContext.Current;
_awaiter.OnCompleted(continuation);
}
public TResult GetResult()
{
OperationContext.Current = _operationContext;
return _awaiter.GetResult();
}
}
}
Usage (a little manual and nesting is untested...):
用法(一些手动和嵌套未经测试......):
/// <summary>
/// Make a call to the service
/// </summary>
/// <param name="action"></param>
/// <param name="endpoint"> </param>
public async Task<ResultCallWrapper<TResult>> CallAsync<TResult>(Func<T, Task<TResult>> action, EndpointAddress endpoint)
{
using (ChannelLifetime<T> channelLifetime = new ChannelLifetime<T>(ConstructChannel(endpoint)))
{
// OperationContextScope doesn't work with async/await
var oldContext = OperationContext.Current;
OperationContext.Current = new OperationContext((IContextChannel)channelLifetime.Channel);
var result = await action(channelLifetime.Channel)
.WithOperationContext(configureAwait: false);
HttpResponseMessageProperty incomingMessageProperty = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name];
string[] keys = incomingMessageProperty.Headers.AllKeys;
var headersOrig = keys.ToDictionary(t => t, t => incomingMessageProperty.Headers[t]);
OperationContext.Current = oldContext;
return new ResultCallWrapper<TResult>(result, new ReadOnlyDictionary<string, string>(headersOrig));
}
}
回答by James Wang
Simple way is to move the await outside the using block
简单的方法是将 await 移动到 using 块之外
public Task<Document> GetDocumentAsync(string docId)
{
var docClient = CreateDocumentServiceClient();
using (new OperationContextScope(docClient.InnerChannel))
{
var task = docClient.GetDocumentAsync(docId);
}
return await task;
}
回答by Franki1986
I am a little bit confused, I found this Blog : Task-based asynchronous operation in WCF
我有点困惑,我找到了这个博客:WCF中基于任务的异步操作
There this is a async wcf communication:
这是一个异步 wcf 通信:
[ServiceContract]
public interface IMessage
{
[OperationContract]
Task<string> GetMessages(string msg);
}
public class MessageService : IMessage
{
async Task<string> IMessage.GetMessages(string msg)
{
var task = Task.Factory.StartNew(() =>
{
Thread.Sleep(10000);
return "Return from Server : " + msg;
});
return await task.ConfigureAwait(false);
}
}
Client:
客户:
var client = new Proxy("BasicHttpBinding_IMessage");
var task = Task.Factory.StartNew(() => client.GetMessages("Hello"));
var str = await task;
So is this also a good way??
所以这也是一个好方法吗??
回答by weichch
Async flow is supported from .Net 4.6.2.
.Net 4.6.2 支持异步流。
We have an ASP.Net WebApi application running on .Net 4.6 where we used the accepted answer. TaskScheduler.FromCurrentSynchronizationContext()
caused deadlock issues when the current synchronization context is AspNetSynchronizationContext
.
我们有一个在 .Net 4.6 上运行的 ASP.Net WebApi 应用程序,我们使用了接受的答案。TaskScheduler.FromCurrentSynchronizationContext()
当前同步上下文为 时导致死锁问题AspNetSynchronizationContext
。
I believe the continuation task was queued after the actual task, causing the actual task is waiting on the continuation whereas the continuation task must run to complete the actual task. i.e. tasks are both waiting on each other.
我相信延续任务在实际任务之后排队,导致实际任务正在等待延续,而延续任务必须运行才能完成实际任务。即任务都在等待对方。
So I fixed the issue by changing using continuation task to use TaskAwaiter. See: https://blogs.msdn.microsoft.com/lucian/2012/12/11/how-to-write-a-custom-awaiter/
因此,我通过将使用延续任务更改为使用 TaskAwaiter 来解决该问题。请参阅:https: //blogs.msdn.microsoft.com/lucian/2012/12/11/how-to-write-a-custom-awaiter/
回答by InteXX
It's been a while on this one, but I'll chime in with my own home-baked solution.
这个已经有一段时间了,但我会用我自己的自制解决方案来补充。
If one doesn't mind doing without OperationContextScope
, one might consider something along these lines:
如果人们不介意不使用OperationContextScope
,则可能会考虑以下方面的事情:
Extension methods
扩展方法
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Intexx.ServiceModel
{
public static class WcfExtensions
{
[DebuggerStepThrough]
public static void Call<TChannel>(this TChannel Client, Action<TChannel> Method) where TChannel : ICommunicationObject
{
try
{
Method.Invoke(Client);
}
finally
{
Cleanup(Client);
}
}
[DebuggerStepThrough]
public static TResult Call<TChannel, TResult>(this TChannel Client, Func<TChannel, TResult> Method) where TChannel : ICommunicationObject
{
try
{
return Method.Invoke(Client);
}
finally
{
Cleanup(Client);
}
}
[DebuggerStepThrough]
public async static Task CallAsync<TChannel>(this TChannel Client, Func<TChannel, Task> Method) where TChannel : ICommunicationObject
{
try
{
await Method.Invoke(Client);
}
finally
{
Cleanup(Client);
}
}
[DebuggerStepThrough]
public async static Task<TResult> CallAsync<TChannel, TResult>(this TChannel Client, Func<TChannel, Task<TResult>> Method) where TChannel : ICommunicationObject
{
try
{
return await Method.Invoke(Client);
}
finally
{
Cleanup(Client);
}
}
private static void Cleanup<TChannel>(TChannel Client) where TChannel : ICommunicationObject
{
try
{
if (Client.IsNotNull)
{
if (Client.State == CommunicationState.Faulted)
Client.Abort();
else
Client.Close();
}
}
catch (Exception ex)
{
Client.Abort();
if (!ex is CommunicationException && !ex is TimeoutException)
throw new Exception(ex.Message, ex);
}
finally
{
Client = null;
}
}
}
}
Client class
客户类
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Reader
{
public class Client
{
public static CemReaderClient Create()
{
Tuple<Channels.Binding, EndpointAddress, double> oService;
try
{
oService = Main.Services(typeof(ICemReader));
return new CemReaderClient(oService.Item1, oService.Item2);
}
catch (KeyNotFoundException ex)
{
return null;
}
}
}
}
Usage(in VB, as the code wouldn't convert)
用法(在 VB 中,因为代码不会转换)
Using oReader As Reader.CemReaderClient = Reader.Client.Create
If oReader.IsNotNothing Then
Dim lIsReading = Await oReader.CallAsync(Function(Reader As Reader.CemReaderClient)
Me.ConfigFilePath = If(Me.ConfigFilePath, Reader.GetConfigFilePath)
Me.BackupDrive = If(Me.BackupDrive, Reader.GetBackupDrive)
Me.SerialPort = If(Me.SerialPort, Reader.GetSerialPort)
Me.LogFolder = If(Me.LogFolder, Reader.GetLogFolder)
Return Reader.GetIsReadingAsync
End Function)
End If
End Using
I've had this running reliably in production under frequency loads of around 15 calls/sec on the client side (that's as fast as serial processing would allow). That was on a single thread, though—this hasn't been rigorously tested for thread safety. YMMV.
我已经在客户端以大约 15 次调用/秒的频率负载在生产中可靠地运行(这是串行处理允许的速度)。不过,那是在单个线程上——这还没有经过严格的线程安全测试。天啊。
In my case, I decided to roll the extension methods into their own private NuGet package. The whole construct has turned out to be pretty handy.
就我而言,我决定将扩展方法滚动到他们自己的私有 NuGet 包中。事实证明,整个构造非常方便。
This will have to be reevaluated, of course, if OperationContextScope
ever ends up being needed.
当然,如果OperationContextScope
最终需要,这将必须重新评估。
The bit with the Tuple
in the Client
class is for Service Discovery support. If anyone would like to see that code as well, give a shout and I'll update my answer.
与位Tuple
在Client
类是服务发现的支持。如果有人也想查看该代码,请大声喊叫,我会更新我的答案。