我為學習目的撰寫了自己的 JobScheduler。這個想法很簡單,它啟動 n 個執行緒,從并發佇列中提取作業/任務,處理它們,一旦完成,它將通知一個事件,以便主執行緒可以等待它完成(如果他愿意的話)。
執行緒回圈看起來像這樣......
internal long ItemCount; // The amount of jobs to process
internal ManualResetEventSlim Event { get; set; } // Event to notify worker threads for new items
internal ConcurrentQueue<JobMeta> Jobs { get; set; } // Jobs
private void Loop(CancellationToken token) {
Loop:
// Break if cancellation is requested
if (token.IsCancellationRequested) return;
// Make threads wait, the event tells them when new jobs arrived
Event.Wait(token);
if (Jobs.TryDequeue(out var jobMeta)) { // Concurrent, dequeue one at a time
// Make other threads wait once no more items are in the queue
if(Interlocked.Decrement(ref ItemCount) == 0) Event.Reset();
jobMeta.Job.Execute(); // Execute job
jobMeta.JobHandle.Set(); // ManualResetEvent.Set to notify the main e.g.
}
goto Loop;
}
// Notify threads about new arrived jobs
public void NotifyThreads() {
Interlocked.Exchange(ref ItemCount, Jobs.Count); // Set ItemCount
Event.Set(); // Notify
}
// Enqueues new job
public JobHandle Schedule(IJob job) {
var handle = new ManualResetEvent(false);
var jobMeta = new JobMeta{ JobHandle = handle, Job = job};
Jobs.Enqueue(jobMeta);
return handle;
}
但是,如果我執行以下操作,有時這會導致死鎖:
var jobHandle = threadScheduler.Schedule(myJob); // JobHandle is a ManualResetEvent
threadScheduler.NotifyThreads();
for(var index = 0; index < 10000; index ){
var otherJobHandle = threadScheduler.Schedule(otherJob);
threadScheduler.NotifyThreads();
otherJobHandle.Wait();
}
jobHandle.Wait(); // Deadlock sometimes...
為什么這會導致死鎖?邏輯問題在哪里?一個普通的 JobScheduler 會是什么樣子(因為我一般找不到關于這個主題的任何好的資訊)?
很高興有任何幫助!
uj5u.com熱心網友回復:
我認為您的最后一個片段不會導致死鎖,因為jobHandle首先入隊,它的句柄應該已經設定。
我可以在您的實施中找到 2 個問題。首先是這一行:
Interlocked.Exchange(ref ItemCount, Jobs.Count);
考慮一下代碼是否按以下順序執行:
//queue.count = 1
1. Jobs.TryDequeue //queue.count = 0
2. Interlocked.Exchange //ItemCount = 0
3. Interlocked.Decrement //ItemCount = -1
ItemCount減少了兩次。所以你應該做的是ItemCount在作業排隊時增加。
Interlocked.Increment(ref ItemCount);
Jobs.Enqueue(jobMeta);
第二個問題在回圈中,想想這段代碼執行順序:
1. Jobs.TryDequeue
2. Interlocked.Decrement
3. Interlocked.Increment
3. Jobs.Enqueue
3. Event.Set
4. Event.Reset
最后等待句柄的狀態未設定,這可能會導致死鎖,因為您的嘗試是應該在排隊作業后設定句柄。在這種情況下,您需要以相反的順序運行代碼:
1. Jobs.TryDequeue
2. Event.Reset // anyway reset the handle
3. if(Interlocked.Decrement > 0)
Event.Set
使用這種方法,無論之前是否有作業入隊Interlocked.Decrement,等待句柄的狀態都是正確的。
上述方法可能不夠高效,因為我們需要在每個回圈中切換等待句柄,我建議您為佇列添加一個 while-loop:
int count = 0;
while(Jobs.TryDequeue(out var jobMeta)) {
count;
...
}
if(count > 0) {
Event.Reset();
if(Interlocked.Add(ref ItemCount, -count) > 0)
Event.Set();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/531094.html
標籤:C#多线程异步僵局
上一篇:c#string.Startwith()List<string>中的任意字串
下一篇:在Windows之間切換
