我在C#中使用了一些線程構造,並遇到了一些無法理解鎖定工作方式的東西。我有一個輔助函數,它接受一個異步任務,並使用一個TaskCompletionSource
成員在多次調用時嘗試同步訪問。第二個線程在第一個線程釋放之前進入鎖定狀態
public static void Main(string[] args)
{
var test = new TestClass();
var task1 = test.Execute("First Task", async() => await Task.Delay(1000));
var task2 = test.Execute("Second Task", async() => await Task.Delay(1000));
task1.Wait();
task2.Wait();
Console.ReadLine();
}
class TestClass : IDisposable
{
private readonly object _lockObject = new object();
private TaskCompletionSource<bool> _activeTaskCompletionSource;
public async Task Execute(string source, Func<Task> actionToExecute)
{
Task activeTask = null;
lock (_lockObject)
{
if (_activeTaskCompletionSource != null)
{
activeTask = _activeTaskCompletionSource.Task;
}
else
{
_activeTaskCompletionSource = new TaskCompletionSource<bool>();
}
}
while (activeTask != null)
{
await activeTask;
lock (_lockObject)
{
if (_activeTaskCompletionSource != null)
{
activeTask = _activeTaskCompletionSource.Task;
}
else
{
activeTask = null;
}
}
}
await actionToExecute();
lock (_lockObject)
{
_activeTaskCompletionSource.SetResult(true);
_activeTaskCompletionSource = null;
}
}
}
這總是最終落入第二個任務的無限循環。我把一些代碼來記錄每一個步驟,因爲它發生,它總是會產生這樣的事情(我#秒後已經手動插入註釋):
[First Task] Waiting for lock (setup) [First Task] Entered lock (setup) [First Task] Grabbing '_activeTaskCompletionSource' (setup) [First Task] Lock released (setup) [First Task] RUNNING ... [Second Task] Waiting for lock (setup) [Second Task] Entered lock (setup) [Second Task] Assigning 'activeTask' (setup) [Second Task] Lock released (setup) [Second Task] Waiting for task to complete ... [First Task] COMPLETED! [First Task] Waiting for lock (cleanup) [First Task] Entered lock (cleanup) [First Task] Setting _activeTaskCompletionSource result ... # Never gets to '_activeTaskCompletionSource = null' # Never gets to 'Releasing lock (cleanup)' for first task [Second Task] Awaited task completed! [Second Task] Waiting for lock (loop) # Immediately enters lock after 'await' is complete # Does not wait for 'First Task' to finish its lock! [Second Task] Entered lock (loop) [Second Task] Assigning 'activeTask' (loop) [Second Task] Lock released (loop) [Second Task] Waiting for task to complete ... [Second Task] Awaited task completed!
這最終將第二個任務進入一個無限循環,因爲_activeTaskCompletionSource
永遠不會回到null
。
我是沒有其他線程所能進入的鎖,直到所有先前的線程都離開它的印象,但在這裏,我First Task
線程永遠不會完成和Second Task
線程爭奪持有它之前釋放其清理鎖。
這是否與混合鎖和異步/等待有關?
它絕對不是建議先task.Wait()或task.Result到一個異步任務,因爲它可以很容易地在一個僵局的情況下結束。 –
@OsmanEsen本例中的'task.Wait()'調用僅用於它在控制檯應用程序中運行,其中_impossible_在主函數中使用async/await。我避免了在所有的異步函數本身以及整個TestClass實現中使用阻塞調用。 – KChaloux