我在并行编程方面有点新,并试图为下一个问题找到解决方案: 让我们有一个简单的功能:
private void doRefreshData()
{
items = getUpdatedData();
}
getUpdatedData()是一个真正的时间comsumpting函数和返回字典。 问题是,函数doRefreshData()可能会立即从多个线程调用。我想实现,只有第一个线程会运行函数,其他线程只是等待结果。简单的lock()不好,因为其他线程也会运行getUpdatedData(),即使没有必要 - 他们已经完成了其他线程的工作。
所以我需要这样的东西:
private void doRefreshData()
{
if (isAlreadyRunning) {
waitForResult();
return;
}
}
items = getUpdatedData();
}
我知道,有一种解决方法,但我确信,已经有解决此问题的方法(Mutex?,Auto / ManualResetEvent?,SomethingElseWithFancyName?)
更新:第一个线程完成工作后,其他人也离开了,有必要允许新线程运行此功能 - 即使几秒钟后,也可以请求刷新新数据。
答案 0 :(得分:4)
您可以使用任务和async
/ await
轻松实现所需的逻辑。
doRefreshData
方法应返回相应类型的Task<TResult>
。这是一个虚拟方法,它在两秒钟内不执行任何操作,然后返回“结果”:
async Task<object> RealGetData()
{
await Task.Delay(2000);
return 42;
}
由于您希望多个方法同时请求数据并使其请求满足相同的结果,因此您需要以某种方式记住已经存在“获取数据”操作。您还需要某种线程同步来消除竞争条件,因此:
private Task<object> currentCalculation;
private object lockTarget = new object(); // just for lock()
现在编写一个async
方法是非常简单的,如果没有待处理的话,它会启动一个新的计算,或者将你挂钩以接收待处理的结果:
async Task<object> GetData()
{
lock (lockTarget)
{
if (currentCalculation != null && !currentCalculation.IsCompleted)
{
return currentCalculation;
}
return currentCalculation = Task.Run<object>(RealGetData);
}
}
使用它非常简单:只要您想要访问“新鲜”数据,请写await GetData()
,然后您将始终获得新结果;代码将自动排队以获取当前计算的结果或为您开始新的计算。例如,这将从单个计算开始,并满足所有10个请求及其结果:
for (var i = 0; i < 10; ++i) {
Console.WriteLine(await GetData());
}
答案 1 :(得分:3)
您可以考虑使用lock
语句实际生成的代码变体,使用Monitor.TryEnter()
代替Monitor.Enter
:
if (Monitor.TryEnter(_myLock))
{
try
{
// Your code
}
finally
{
Monitor.Exit(_myLock);
}
}
else
{
// Do something else
}
然后,如果其他线程已经获得锁定,则可以改为执行其他操作。
答案 2 :(得分:2)
如果只需要开一次,我建议使用Lazy<T>
。实际上,我会使用Task<T>
和一些Interlocked
来电。类似的东西:
private Task<DataValue> _pendingRefresh;
private void doRefreshData()
{
var tcs = new TaskCompletionSource<DataValue>();
//See if there's one in flight
var task = Interlocked.CompareExchange(ref _pendingRefresh, tcs.Task, null);
if (task != null)
{
task.Wait(); //Or async, etc
var items = task.Result;
}
else
{
try{
var items = getUpdatedData();
tcs.SetResult(items); //?
} catch (Exception ex) {
tcs.SetException(ex);
throw;
} finally {
//Allow a new call to run
Interlocked.Exchange(ref _pendingRefresh, null);
}
}
}
基本上,只有一位来电者可以将_pendingRefresh
从null
更改为null
。然后它将完成工作,并在工作完成后发出Task
信号。其他来电者会进入if
的上半部分,并等待Task
被标记为完整。
一旦被允许进行工作的&#34;来电者&#34;已完成并发出任务信号,它将_pendingRefresh
设置回null
。允许新呼叫者完成工作并拨打新电话。
请注意,我还添加了一些更好的错误处理功能,以便在getUpdatedData
抛出异常时,您不会因为等待任务完成该任务而导致大量呼叫者陷入困境永不。他们会收到错误,但至少你会在所有受影响的线程上了解它。
答案 3 :(得分:0)
我的建议是在这里使用一个名为Mutex。在这里命名的原因是为了更容易设置共享同步点。仅在跨线程共享互斥锁时,不命名互斥锁。你可以在这里做到这一点,但我发现命名它们更容易。
// Create a new named mutex and try to obtain a lock on it immediately.
// The WaitOne operation below ensures that you will have to wait when
// another thread has the lock.
Mutex mutex = new Mutex(true,"SynchronisationPoint");
// Wait for the mutex to become available.
// If you get beyond this point it means that you are the only
// one performing this operation.
mutex.WaitOne();
// Check if the operation was already completed.
// When it was, do nothing here.
if(isCompleted) {
return;
}
// Perform the operation.
items = getUpdatedData();