DeadLock для task.Wait () с задачей, которые редактируют пользовательский интерфейс - PullRequest
0 голосов
/ 09 ноября 2018

Я пытаюсь найти здесь некоторые решения моей проблемы, но безрезультатно (или я просто не понимаю их правильно), поэтому, если кто-нибудь сможет помочь / объяснить, я буду очень благодарен.

Я просто разрабатываю инструмент для системных администраторов, использующий Win Form, и теперь мне нужно создать непрерывный пинг на выбранной машине, которая работает в фоновом режиме. На интерфейсе есть индикатор состояния Online, который мне нужно отредактировать с помощью фонового пинга. Так что сейчас я нахожусь в таком состоянии:

Класс 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 (класс фонового пинга)

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);
        }
    }
}

Таким образом, проблема в тупике, когда я отменяю токен и жду () для завершения задачи -> она все еще выполняется, но пока (...) в задаче выполнена правильно.

1 Ответ

0 голосов
/ 09 ноября 2018

У вас тупик, потому что ping.Wait(); блокирует поток пользовательского интерфейса.

Вы должны ждать задачи асинхронно, используя 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().

Подобная проблема и решение описаны здесь - Не блокировать при асинхронном коде

Следует избегать синхронного ожидания при выполнении задач, поэтому всегда следует использовать await с задачами вместо Wait() или Result. Также, как указано @ Fildor , вы должны использовать async-await полностью, чтобы избежать таких ситуаций.

...