Фон
У нас есть сервисная операция, которая может принимать одновременные асинхронные запросы и должна обрабатывать эти запросы по одному.
В следующем примере метод UploadAndImport(...)
получает параллельные запросы в нескольких потоках, но его вызовы метода ImportFile(...)
должны выполняться по одному за раз.
Описание личности
Представьте себе склад со множеством рабочих (несколько потоков). Люди (клиенты) могут отправлять на склад много пакетов (запросов) одновременно (одновременно). Когда посылка приходит в себя, работник берет на себя ответственность за нее от начала до конца, и человек, уронивший посылку, может уйти (запустить и забыть). Задача рабочих состоит в том, чтобы складывать каждую упаковку в небольшой желоб, и только один работник может одновременно положить упаковку в желоб, иначе наступит хаос. Если человек, который бросил пакет, регистрируется позже (конечная точка опроса), склад должен иметь возможность сообщить, прошел пакет по желобу или нет.
Вопрос
Тогда возникает вопрос, как написать служебную операцию, которая ...
- может получать параллельные клиентские запросы,
- получает и обрабатывает эти запросы в нескольких потоках,
- обрабатывает запросы в том же потоке, который получил запрос,
- обрабатывает запросы по одному,
- - это односторонняя операция «забей и забудь», а
- имеет отдельную конечную точку опроса, которая сообщает о завершении запроса.
Мы попробовали следующее и задаемся вопросом:
- Существуют ли какие-либо расовые условия, которые мы не рассмотрели?
- Есть ли более канонический способ кодирования этого сценария в C # .NET с сервис-ориентированной архитектурой (мы используем WCF)?
Пример: что мы попробовали?
Это сервисный код, который мы попробовали. Это работает, хотя это похоже на что-то вроде хака или клуджа.
static ImportFileInfo _inProgressRequest = null;
static readonly ConcurrentDictionary<Guid, ImportFileInfo> WaitingRequests =
new ConcurrentDictionary<Guid, ImportFileInfo>();
public void UploadAndImport(ImportFileInfo request)
{
// Receive the incoming request
WaitingRequests.TryAdd(request.OperationId, request);
while (null != Interlocked.CompareExchange(ref _inProgressRequest, request, null))
{
// Wait for any previous processing to complete
Thread.Sleep(500);
}
// Process the incoming request
ImportFile(request);
Interlocked.Exchange(ref _inProgressRequest, null);
WaitingRequests.TryRemove(request.OperationId, out _);
}
public bool UploadAndImportIsComplete(Guid operationId) =>
!WaitingRequests.ContainsKey(operationId);
Это пример кода клиента.
private static async Task UploadFile(FileInfo fileInfo, ImportFileInfo importFileInfo)
{
using (var proxy = new Proxy())
using (var stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
importFileInfo.FileByteStream = stream;
proxy.UploadAndImport(importFileInfo);
}
await Task.Run(() => Poller.Poll(timeoutSeconds: 90, intervalSeconds: 1, func: () =>
{
using (var proxy = new Proxy())
{
return proxy.UploadAndImportIsComplete(importFileInfo.OperationId);
}
}));
}
Трудно написать минимальный жизнеспособный пример этого на скрипке, но вот начало , которое дает смысл и которое компилируется.
Как и прежде, вышеприведенное выглядит как хак / клудж, и мы спрашиваем как о потенциальных подводных камнях в его подходе, так и об альтернативных шаблонах, которые являются более подходящими / каноническими.