首页 > 解决方案 > 使用manualResetEvent的死锁 - c#

问题描述

在 My Sample UI 中,有 3 个按钮。开始按钮,暂停,继续。开始按钮是 async/await 并且会调用一个方法:

private async void btnStart_Click()
{
    await Task.Run(() => StartingMethod());
}

我们有一个ManualResetEvent名为_pauseEvent. 这是结构StartingMethod()

public void StartingMethod()
{
   for (int i =0; i < 1000000; i++)
   {
      _pauseEvent.WaitOne();
      // Do Something
   }
}

这是暂停/恢复按钮:

private async void btnPause_Click()
{
    await Task.Run(() => _pauseEvent.Reset());
}

private async void btnResume_Click()
{
    await Task.Run(() => _pauseEvent.Set());
}

但有时它会The handle is invalidManualResetEvent暂停/恢复通话提供帮助。这似乎是一个竞争条件问题。所以我决定创建一个全局锁对象。

Object _lockObject = new object();

这是新的:

public void StartingMethod()
{
   for (int i =0; i < 1000000; i++)
   {
      lock (_lockObject)
         _pauseEvent.WaitOne();
      // Do Something
   }
}

private async void btnPause_Click()
{
    await Task.Run(() => lock (_lockObject) _pauseEvent.Reset());
}

private async void btnResume_Click()
{
    await Task.Run(() => lock (_lockObject) _pauseEvent.Set());
}

但看来,在这里,我又将面临锁定对象的死锁问题。我该如何处理这种情况?

标签: c#deadlockmanualresetevent

解决方案


我很确定使用async void,Task.Runlock导致死锁。我建议做的是使用 aCancellationToken和一些中间状态来表示StartingMethod.

请注意,它不会处理许多StartingMethods 的并发执行,您需要使用类似的东西CanExecute来防止用户连续多次单击开始按钮。

private int _state = 0;

public void StartingMethod(CancellationToken cancellation, bool resetState)
{
   if (resetState)
   {
       _state = 0;
      // reset your additional data  
   }
   for (int i = _state; i < 1000000; i++)
   {
      if (!cancellation.IsCancellationRequested)
      { 
          // Recover saved state
          // Do Something
      }
      else
      {
          // save intermediate state
          // save your additional data
          _state = i;
      }
   }
}

我强烈建议将 try-catch 添加到async方法中:

private CancellationTokenSource _cts;

private async Task HandleStart(bool resetState)
{ 
   _cts = new CancellationTokenSource();
    try
    {
        await Task.Run(() => StartingMethod(_cts.Token, resetState));
    }
    catch (Exception e)
    {
    // log or show some error on UI
    }
    finally
    {
        _cts.Dispose();
    }
}

private void btnStart_Click()
{
    // No need to await because it's a single statement in a method
    HandleStart(true);
}

private async void btnPause_Click()
{
    _cts.Cancel();
}

private void btnResume_Click()
{
    HandleStart(false);
}

推荐阅读