Reputation: 15934
We have an asynchronous WCF service operation that gets log files from all of the different components of our system and sends them to the client. Since this could take a while if one of the components isn't working right, it would be nice if this functionality won't time out, but it shouldn't cause the client to hang either.
My understanding of asynchronous WCF services is that when the client asks the server for something, the server immediately responds with a message saying, "I'm on it. Keep on doing your own stuff, and I'll let you know when I'm finished." Then the connection is freed up for the client to make other requests while the server spins up a new thread to do the bulk of its work. When the server is finished, it sends a message back to the client with the results. Because of this, the connection between the server and client is free, and regardless of how long the server takes, the connection should never time out. Is this correct?
If that's the case, then our service isn't working as expected. When I test the service, it works as expected as long as it takes less than a minute. If I force it to take longer than that, though, the client throws a TimeoutException. Since the service is asynchronous, shouldn't it never time out? If so, what am I missing?
We wrote our asynchronous service using this page as a guide: http://code.msdn.microsoft.com/windowsdesktop/How-to-Implement-a-WCF-2090bec8
Here is my code. This is the service contract:
[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...
}
This is the service implementation:
[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...
}
Here is the client-side code:
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...
}
Upvotes: 1
Views: 5138
Reputation: 6549
There is a timeout set in your config file even for asynchronous calls. You should probably increase it if it will take a long time to respond. I think default is 1 minute. In visual studio, go to tools-> WCF Service Configuration Editor to easily change the value.
This may also help you if you want to see what the configuration looks like: Increasing the timeout value in a WCF service
You can set it in that config file, or in the code behind.
Upvotes: 2