我们有一个异步WCF服务操作,它从我们系统的所有不同组件获取日志文件并将它们发送到客户端。由于这可能需要一段时间,如果其中一个组件不能正常工作,那么如果此功能不会超时会很好,但它不应该导致客户端挂起。
我对异步WCF服务的理解是,当客户端向服务器询问某些内容时,服务器立即响应一条消息,说“我正在使用它。继续做自己的事情,我会让你知道我结束的时候。“然后释放连接,以便客户端在服务器旋转新线程以完成大部分工作时发出其他请求。服务器完成后,它会将结果发送回客户端。因此,服务器和客户端之间的连接是免费的,无论服务器需要多长时间,连接都不应该超时。这是对的吗?
如果是这种情况,那么我们的服务没有按预期工作。当我测试服务时,只要不到一分钟,它就会按预期工作。但是,如果我强制它花费更长时间,则客户端会抛出TimeoutException。由于服务是异步的,它不应该永远不会超时吗?如果是这样,我错过了什么?
我们使用此页面作为指南编写了异步服务: http://code.msdn.microsoft.com/windowsdesktop/How-to-Implement-a-WCF-2090bec8
这是我的代码。这是服务合同:
[ServiceContract(CallbackContract = typeof(IInformationServiceCallBack), SessionMode = SessionMode.Required)]
public interface IInformationService
{
//snip...
[OperationContract(AsyncPattern=true)]
[FaultContract(typeof(LogFileFault))]
IAsyncResult BeginGetLogFiles(LogFileRequest[] logfileRequests,
AsyncCallback callback, object state);
LogFile[] EndGetLogFiles(IAsyncResult result);
//snip...
}
这是服务实现:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerSession, UseSynchronizationContext=false)]
public class InformationServiceImpl : IInformationService, IDisposable
{
//snip...
public IAsyncResult BeginGetLogFiles(LogFileRequest[] logfileRequests,
AsyncCallback callback, object state)
{
var task = Task<LogFile[]>.Factory.StartNew((x) =>
{
return GetLogFilesHelper(logfileRequests);
}, state);
return task.ContinueWith(res => callback(task));
}
public LogFile[] EndGetLogFiles(IAsyncResult result)
{
var castResult = result as Task<LogFile[]>;
return castResult.Result;
}
private LogFile[] GetLogFilesHelper(LogFileRequest[] logfileRequests)
{
//Long-running method that gets the log files
}
//snip...
}
这是客户端代码:
public class InformationServiceConnection : WcfDurableConnection<IInformationService> //WcfDurableConnection is one of our internal classes
{
//snip...
public void GetServiceLogFiles(Action<LogFile[], WcfCommandResult> callback)
{
var logfileRequests = new LogFileRequest[]
{
new LogFileRequest(/* snip */),
new LogFileRequest(/* snip */),
new LogFileRequest(/* snip */),
new LogFileRequest(/* snip */)
};
ExecuteTask(x =>
{
LogFile[] logfile = null;
WcfCommandResult wcfResult = null;
var asyncCallback = new AsyncCallback((result) =>
{
logfile = Channel.EndGetLogFiles(result);
callback(logfile, wcfResult);
});
wcfResult = RunCommand(y =>
{
Channel.BeginGetLogFiles(logfileRequests, asyncCallback, null);
}, x);
});
}
/* ExecuteTask and RunCommand are both methods that take care of
* multithreading issues for us. I included their code below in
* case they make a difference, but the code I'm most interested
* in is the GetServiceLogFiles method above. */
//snip...
protected CancellationTokenSource ExecuteTask(Action<CancellationToken> action)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
ManualResetEvent lastTask;
ManualResetEvent thisTask;
lock (_objectLock)
{
lastTask = _syncTask;
thisTask = new ManualResetEvent(false);
_syncTask = thisTask;
}
tokenSource.Token.Register(x => ((ManualResetEvent)x).Set(), thisTask);
var task = Task.Factory.StartNew((x) =>
{
try
{
lastTask.WaitOne();
action((CancellationToken)x);
}
catch (Exception e)
{
LogUtility.Error(e);
}
finally
{
thisTask.Set();
}
}, tokenSource.Token, tokenSource.Token).HandleExceptions();
return tokenSource;
}
//snip...
protected WcfCommandResult RunCommand(Action<CancellationToken> action, CancellationToken token, bool isRestarting = false)
{
return RunCommand(x => { action(x); return true; }, token, isRestarting);
}
protected WcfCommandResult RunCommand(Func<CancellationToken, bool> action, CancellationToken token, bool isRestarting = false)
{
WcfCommandResult result = new WcfCommandResult();
lock (_reconnectionLock)
{
if (_reconnecting && !isRestarting)
{
result.Completed = false;
return result;
}
}
lock (_channelLock)
{
if (Channel == null && !_closing)
{
token.ThrowIfCancellationRequested();
Channel = GetNewChannel();
var iChannel = (IClientChannel)Channel;
var initResult = Initialize(token, false);
if (initResult.Completed)
{
Connected = true;
LogUtility.Info(string.Format("Connected to {0} at {1}", ServiceName, iChannel.RemoteAddress));
}
else
LogUtility.Info(string.Format("Failed to connect to {0} at {1}", ServiceName, iChannel.RemoteAddress));
}
}
try
{
var channel = Channel;
token.ThrowIfCancellationRequested();
if (channel != null)
result.Completed = action(token);
}
catch (FaultException e)
{
result.Exception = e;
result.Detail = e.GetDetail<DurableFault>();
LogUtility.Error(result.Exception);
}
catch (CommunicationException e)
{
Connected = false;
result.Exception = e;
IClientChannel channel = ((IClientChannel)Channel);
if (channel != null)
channel.Abort();
Channel = null;
if (!_reconnecting)
LogUtility.Error(result.Exception);
}
catch (TimeoutException e)
{
Connected = false;
result.Exception = e;
IClientChannel channel = ((IClientChannel)Channel);
if (channel != null)
channel.Abort();
Channel = null;
if (!_reconnecting)
LogUtility.Error(result.Exception);
}
catch (NullReferenceException e)
{
Connected = false;
result.Exception = e;
IClientChannel channel = ((IClientChannel)Channel);
if (channel != null)
channel.Abort();
Channel = null;
if (!_reconnecting)
LogUtility.WriteException("Channel is null, it has either been disposed or not setup, call BeginSetupUser to create a new channel", e);
}
catch (ObjectDisposedException e)
{
Connected = false;
result.Exception = e;
IClientChannel channel = ((IClientChannel)Channel);
if (channel != null)
channel.Abort();
Channel = null;
if (!_reconnecting)
LogUtility.Error(result.Exception);
}
catch (InvalidOperationException e)
{
Connected = false;
result.Exception = e;
IClientChannel channel = ((IClientChannel)Channel);
if (channel != null)
channel.Abort();
Channel = null;
if (!_reconnecting)
LogUtility.Error(result.Exception);
}
return result;
}
//snip...
}
答案 0 :(得分:2)
即使是异步调用,配置文件中也会设置超时。如果需要很长时间才能响应,您应该增加它。我认为默认是1分钟。在visual studio中,转到tools-&gt; WCF服务配置编辑器可以轻松更改值。
如果您想查看配置的内容,这也可能对您有所帮助:Increasing the timeout value in a WCF service
您可以在该配置文件或后面的代码中设置它。