【发布时间】:2018-05-05 00:09:37
【问题描述】:
背景
我们有一个可以接收并发异步请求的服务操作,并且必须一次处理一个请求。
在以下示例中,UploadAndImport(...) 方法在多个线程上接收并发请求,但它对 ImportFile(...) 方法的调用必须一次发生一个。
外行描述
想象一个有许多工人(多线程)的仓库。人们(客户)可以同时(并发)向仓库发送多个包裹(请求)。当一个包裹进来时,工人从头到尾负责它,而放下包裹的人可以离开(即发即弃)。工人的工作是将每个包裹放入一个小滑槽,一次只能有一个工人将一个包裹放入滑槽,否则会出现混乱。如果投递包裹的人稍后签到(轮询端点),仓库应该能够报告包裹是否从滑槽下落。
问题
那么问题是如何编写一个服务操作...
- 可以接收并发的客户端请求,
- 在多个线程上接收和处理这些请求,
- 在接收请求的同一线程上处理请求,
- 一次处理一个请求,
- 是一种单向即发即弃的操作,并且
- 有一个单独的轮询端点,用于报告请求完成情况。
我们尝试了以下方法并且想知道两件事:
- 有没有我们没有考虑过的竞争条件?
- 是否有更规范的方式在 C#.NET 中使用面向服务的架构(我们碰巧使用 WCF)编写此场景?
示例:我们尝试了什么?
这是我们尝试过的服务代码。尽管感觉有点像 hack 或 kludge,但它确实有效。
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);
}
}));
}
很难在 Fiddle 中编写一个最小可行的示例,但 here is a start 给出了一种感觉并且可以编译。
与以前一样,上述内容似乎是一种 hack/kludge,我们正在询问其方法中的潜在缺陷以及更合适/规范的替代模式。
【问题讨论】:
-
您说它必须在单独的线程上处理请求,但在原始线程上完成。处理和完成有什么区别?
-
我的意思是接收请求的同一个线程也必须完成请求。
-
请根据您的问题定义“完整”并告诉我它与“处理”有何不同。另外,如果完成发生在同一个线程上,那么有一个端点来轮询状态的目的是什么?
-
@JohnWu 我已经将“完成”和“处理”替换为“过程”一词,以使情况更清晰。
-
@JohnWu 进行轮询的原因是服务操作是单向的,即发即弃操作。
标签: c# .net multithreading asynchronous concurrency