TimeoutException при одновременных вызовах служб WCF из приложения Silverlight
Анализируя файлы журналов, я заметил, что ~1% вызовов служб заканчивался исключением TimeoutException на стороне клиента Silverlight. Сервисы (wcf) довольно просты и не выполняют длинных вычислений. Согласно журналу, все обращения к сервисам всегда обрабатываются менее чем за 1 секунду (даже когда на клиенте происходит TimeoutException!), Поэтому это не тайм-аут сервера.
Так что не так? Это может быть проблема конфигурации или сети? Как я могу избежать этого? Какие дополнительные данные журналов могут быть полезны для локализации этой проблемы?
Единственный обходной путь, который я придумал, - это повторить вызовы службы после истечения времени ожидания.
Я буду признателен за любую помощь в этом вопросе!
Обновление: При запуске приложение выполняет 17 сервисных вызовов и 12 из них одновременно (может ли это быть причиной сбоя?).
Обновление: журнал WCF не содержит полезной информации об этой проблеме. Кажется, некоторые сервисные вызовы не доходят до серверной части.
3 ответа
Проблема заключается в максимальном количестве одновременных подключений к одному серверу в Internet Explorer 7/6. Это только 2! http://msdn.microsoft.com/en-us/library/cc304129(VS.85).aspx
Если у нас есть 3 (например, одновременных) сервисных вызова, два из них будут отправлены на сервер немедленно, а третий будет ожидать в очереди. Также таймер отправки (соответствует sendTimeout
) выполняется, когда запрос находится в очереди. Если первые два запроса на обслуживание будут выполняться в течение длительного времени, то третий вызовет исключение TimeoutException, хотя оно не было отправлено на сервер (и мы не увидим никакой информации об этом запросе на стороне сервера и не сможем перехватить его с помощью Fiddler)....).
В более реальной ситуации, если у нас около 12 одновременных вызовов и время пересылки по умолчанию составляет 1 мин, а если сервисные вызовы обрабатывают в среднем более 10 секунд, мы можем легко получить исключение тайм-аута для двух последних вызовов (12 / 2 * 10 сек = 60 сек.) потому что они будут ждать всех остальных.
Решение:
- Минимизируйте количество одновременных вызовов.
- Увеличение
sendTimeout
значение в конфигурации клиента. - Реализовать функцию автоматического повтора для критически важных служб.
- Реализуйте очередь запросов для управления ими.
В моем случае я сделал 1-3 вещи, и этого было достаточно.
Вот моя реализация функции автоматического повтора:
public static class ClientBaseExtender
{
/// <summary>
/// Tries to execute async service call. If <see cref="TimeoutException"/> occured retries again.
/// </summary>
/// <typeparam name="TChannel">ServiceClient class.</typeparam>
/// <typeparam name="TArgs">Type of service client method return argument.</typeparam>
/// <param name="client">ServiceClient instance.</param>
/// <param name="tryExecute">Delegate that execute starting of service call.</param>
/// <param name="onCompletedSubcribe">Delegate that subcribes an event handler to the OnCompleted event of the service client method.</param>
/// <param name="onCompleted">Delegate that executes when service call is succeeded.</param>
/// <param name="onError">Delegate that executes when service call fails.</param>
/// <param name="maxAttempts">Maximum attempts to execute service call before error if <see cref="TimeoutException"/> occured (by default 5).</param>
public static void ExecuteAsyncRepeatedly<TChannel, TArgs>(this ClientBase<TChannel> client, Action tryExecute,
Action<EventHandler<TArgs>> onCompletedSubcribe, EventHandler<TArgs> onCompleted,
EventHandler<TArgs> onError, int maxAttempts)
where TChannel : class
where TArgs : AsyncCompletedEventArgs
{
int attempts = 0;
var serviceName = client.GetType().Name;
onCompletedSubcribe((s, e) =>
{
if (e.Error == null) // Everything is OK
{
if (onCompleted != null)
onCompleted(s, e);
((ICommunicationObject)client).Close();
Debug.WriteLine("[{1}] Service '{0}' closed.", serviceName, DateTime.Now);
}
else if (e.Error is TimeoutException)
{
attempts++;
if (attempts >= maxAttempts) // Final timeout after n attempts
{
Debug.WriteLine("[{2}], Final Timeout occured in '{0}' service after {1} attempts.", serviceName, attempts, DateTime.Now);
if (onError != null)
onError(s, e);
client.Abort();
Debug.WriteLine("[{1}] Service '{0}' aborted.", serviceName, DateTime.Now);
return;
}
// Local timeout
Debug.WriteLine("[{2}] Timeout occured in '{0}' service (attempt #{1}).", serviceName, attempts, DateTime.Now);
Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
tryExecute(); // Try again.
}
else
{
if (onError != null)
onError(s, e);
client.Abort();
Debug.WriteLine("[{1}] Service '{0}' aborted.", serviceName, DateTime.Now);
}
});
Debug.WriteLine("[{2}] Attempt #{0} to execute call to '{1}' service.", attempts + 1, serviceName, DateTime.Now);
tryExecute(); // First attempt to execute
}
}
И вот использование:
var client = new MyServiceClient();
client.ExecuteAsyncRepeatedly(() => client.MyOperationAsync(...),
(EventHandler<MyOperationCompletedEventArgs> handler) => client.MyOperationCompleted += handler,
(s, e) => // OnCompleted
{
Do(e.Result);
},
(s, e) => // OnError
{
HandleError(e.Error);
}
);
Надеюсь, это будет полезно.
У вас все еще есть эта проблема?
Если так, то, возможно, вам следует смотреть сеть с помощью Fiddler или Microsoft Network Monitor или чего-то еще?
Мммм... может ли быть так, что запрос / ответ занимает более 64 К или слишком много объектов сериализовано?
Можете ли вы попытаться сделать симуляцию на сервере с консольным приложением (просто чтобы проверить, является ли он сетевым, SL...)?