【问题标题】:DeadLock on task.Wait() with Task which edit UI使用编辑 UI 的任务在 task.Wait() 上死锁
【发布时间】:2018-11-09 07:52:27
【问题描述】:

我正在尝试在这里找到一些解决我的问题的方法,但没有结果(或者我只是没有得到正确的解决方案)所以如果有人可以帮助/解释我将非常感激。

我只是在为使用 Win Form 的系统管理员开发一个工具,现在我需要在后台运行的选定机器上创建一个连续 ping。 UI 上有一个在线状态指示器,我需要使用后台 ping 进行编辑。所以现在我处于这种状态:

A级(获胜形式):

ClassB activeRelation = new ClassB();

public void UpdateOnline(Relation pingedRelation)
{
    //There is many Relations at one time, but form shows Info only for one...
    if (activeRelation == pingedRelation)
    {
        if (p_Online.InvokeRequired)
        {
            p_Online.Invoke(new Action(() =>
                p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure
            ));
        }
        else
        {
            p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure;
        }
    }
}

//Button for tunring On/Off the background ping for current machine
private void Btn_PingOnOff_Click(object sender, EventArgs e)
{
    Button btn = (sender is Button) ? sender as Button : null;

    if (btn != null)
    {
        if (activeRelation.PingRunning)
        {
            activeRelation.StopPing();
            btn.Image = Properties.Resources.Switch_Off;
        }
        else
        {
            activeRelation.StartPing(UpdateOnline);
            btn.Image = Properties.Resources.Switch_On;
        }
    }
}

B 类(代表与某些机器的关系的类)

private ClassC pinger;    

public void StartPing(Action<Relation> action)
{
    pinger = new ClassC(this);
    pinger.PingStatusUpdate += action;
    pinger.Start();
}

public void StopPing()
{
    if (pinger != null)
    {
        pinger.Stop();
        pinger = null;
    }
}

C类(后台ping类)

private bool running = false;
private ClassB classb;
private Task ping;
private CancellationTokenSource tokenSource;
public event Action<ClassB> PingStatusUpdate;

public ClassC(ClassB classB)
{
    this.classB = classB;
}

public void Start()
{
    tokenSource = new CancellationTokenSource();
    CancellationToken token = tokenSource.Token;

    ping = PingAction(token);
    running = true;
}

public void Stop()
{
    if (running)
    {
        tokenSource.Cancel();
        ping.Wait(); //And there is a problem -> DeadLock
        ping.Dispose();
        tokenSource.Dispose();
    }

    running = false;
}

private async Task PingAction(CancellationToken ct)
{
    bool previousResult = RemoteTasks.Ping(classB.Name);
    PingStatusUpdate?.Invoke(classB);

    while (!ct.IsCancellationRequested)
    {
        await Task.Delay(pingInterval);

        bool newResult = RemoteTasks.Ping(classB.Name);

        if (newResult != previousResult)
        {
            previousResult = newResult;
            PingStatusUpdate?.Invoke(classB);
        }
    }
}

所以当我取消令牌和 Wait() 以完成任务时问题处于死锁状态 -> 它仍在运行,但任务中的 While(...) 已正确完成。

【问题讨论】:

  • 请提供一个可验证的完整示例。这是太多的代码,而且很有可能,只需制作示例即可解决问题。
  • @Christopher 很抱歉,感谢您的建议,但我真的尝试在这里编写尽可能少的代码。我认为所有这些代码都是解决问题所必需的。但我将尝试删除更多代码。 :)

标签: c# winforms asynchronous task


【解决方案1】:

您遇到了死锁,因为ping.Wait(); 阻塞了 UI 线程。

您应该使用await 异步等待任务。

所以,如果Stop() 是事件处理程序,则将其更改为:

public async void Stop() // async added here
{
    if (running)
    {
        tokenSource.Cancel();
        await ping; // await here
        ping.Dispose();
        tokenSource.Dispose();
    }

    running = false;
}

如果不是:

public async Task Stop() // async added here, void changed to Task
{
    if (running)
    {
        tokenSource.Cancel();
        await ping; // await here
        ping.Dispose();
        tokenSource.Dispose();
    }

    running = false;
}

正如 @JohnB 所述,异步方法应具有Async 后缀,因此该方法应命名为StopAsync()

这里解释了类似的问题和解决方案 - Do Not Block On Async Code

您应该避免同步等待任务,因此您应该始终将await 与任务一起使用,而不是Wait()Result。此外,正如 @Fildor 所指出的,您应该一直使用 async-await 来避免这种情况。

【讨论】:

  • 命名应该是StopAsync...但嘿只是挑剔
  • 如果你这样做,我认为值得一提的是你可能只是推迟了问题。要么“一直异步”,要么根本不去。
  • 很好又快速的解释,你是对的,现在一切都像魅力一样。从现在开始我更聪明了,非常感谢你。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-03
  • 1970-01-01
  • 2015-06-13
相关资源
最近更新 更多