Асинхронный вызов функции Windows Forms C# - PullRequest
3 голосов
/ 01 февраля 2020

У меня есть асинхронная функция. Он вызывается только один раз, когда форма отображается впервые. Моя функция должна пинговать устройства асинхронно, когда я открываю программу. Но оказывается, что когда вы закрываете дочернюю форму, запускается другой опрос. Подскажите где может быть ошибка.

Вызов функции (я пытался вызвать ее в formLoad):

private async void MainForm_Shown(object sender, EventArgs e)
{
    await Start();
} 

Сама функция:

public async Task Start()
{
    while (keyOprosDev)
    {
        for (int i = 0; i < devicesListActivity.Count; i++)
        {
            devicesListActivity[i].DevicesList.DevicesTotalPing++;

            string ipAdresDevice = devicesListActivity[i].DevicesList.DevicesName;
            int portDevice = devicesListActivity[i].DevicesList.DevicesPort;
            int activeDevice = devicesListActivity[i].DevicesList.DevicesActiv;
            int sendTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeSend;
            int respTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeResp;

            using (TcpClient client = new TcpClient())
            {
                if (activeDevice == 1)
                {
                    client.SendTimeout = sendTimeDevice;
                    client.ReceiveTimeout = respTimeDevice;

                    var ca = client.ConnectAsync(ipAdresDevice, portDevice);
                    await Task.WhenAny(ca, Task.Delay(sendTimeDevice));

                    client.Close();

                    if (ca.IsFaulted || !ca.IsCompleted)
                    {
                        textBox1.AppendText($"{DateTime.Now.ToString()} Server refused connection." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
                        textBox1.AppendText("\r\n");
                        devicesListActivity[i].DevicesList.DevicesImage = 1;

                    }

                    else
                    {
                        devicesListActivity[i].DevicesList.DevicesSuccessPing++;
                        textBox1.AppendText($"{DateTime.Now.ToString()} Server available" + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
                        textBox1.AppendText("\r\n");
                        devicesListActivity[i].DevicesList.DevicesImage = 2;
                    }
                }
                else
                {

                }                                                   
            }
            await Task.Delay(interval);
        }                    
    }
}

И вот Открытие дочерней формы:

try
    {
        DbViewer dbViewer = new DbViewer();
        dbViewer.FormClosed += new FormClosedEventHandler(refr_FormClosed);
        dbViewer.ShowDialog();
    }
    catch (Exception ex)
    {
        writeEventInDb(ex.Message);
    }

Это событие, которое обрабатывает закрытие дочерней формы:

void refr_FormClosed(object sender, FormClosedEventArgs e)
{
    try
    {
        kryptonTreeView1.Nodes[0].Nodes[0].Nodes.Clear();
        kryptonTreeView1.Nodes[0].Nodes[1].Nodes.Clear();

        loadIpListFromDb();
        loadComListFromDb();

        kryptonTreeView1.ExpandAll();
    }
    catch (Exception ex)
    {
        writeEventInDb(ex.Message);
    }
}

1 Ответ

3 голосов
/ 02 февраля 2020

Вам нужно передать токен отмены. Где-то за пределами этого кода вам нужно создать CancellationTokenSource, лучшее место, вероятно, это свойство вида:

class MainForm
{
    CancellationTokenSource cts;
    ...

Затем вы инициализируете это и передайте это Start():

private async void MainForm_Shown(object sender, EventArgs e)
{
    cts = new CancellationTokenSource();
    CancellationToken ct = cts.Token;
    await Start(ct);
}

При запуске l oop вам необходимо отслеживать токен отмены:

Поскольку вы используете задержку для тайм-аута ConnectAsync() вам нужно Task.Delay(), чтобы знать, когда запрашивается отмена, поэтому вам нужно передать токен в Task.Delay ():

await Task.WhenAny(ca, Task.Delay(sendTimeDevice,ct));

После TcpClient.Close() вам нужно проверить, запрашивается ли отмена и остановите циклы, если это:

if (ct.IsCancellationRequested)
    break;

Вам нужно будет выполнить тот же тест в while loop, а также выполнить его непосредственно перед ConnectAsync(). Хотя наиболее вероятное место, с которым вы столкнетесь ct.IsCancellationRequested == true, будет сразу после Task.WhenyAny или сразу после интервала L oop, нет смысла начинать ConnectAsync(), если была запрошена отмена.

Вы должны также передайте CancellationToken с интервалом L oop, в противном случае вы можете ждать interval до закрытия формы:

// This will throw an OperationCancelled Exception if it is cancelled.
await Task.Delay(interval,ct);

Потому что вы все равно продолжите и просто выйдите, если отменено зарегистрировано Вы могли бы избежать написания отдельной попытки / захвата, которая ничего не делает и ожидает такой интервал, он почти наверняка менее эффективен, но он чище.

// Leave any exceptions of Task.Delay() unobserved and continue
await Task.WhenAny(Task.Delay(interval,ct));

Наконец, вам нужно избавиться от CancellationTokenSource, я полагаю, вы будет делать это в чем-то вроде MainForm_Closed() функции?

private void MainForm_Closed(object sender, EventArgs e)
{
    cts.Dispose();

Единственное, что остается сделать, это работать, когда вы хотите запустить запрос CancellationRequest, основываясь на том, что вы сказали, что хотите сделать это когда кнопка закрытия формы была нажата, так:

private void MainForm_Closing(object sender, EventArgs e)
{
    cts.Cancel();

Это приведет к отмене Если вы переходите в отмененное состояние, ваша подпрограмма Start() увидит это и выйдет.

В вашем коде нет единого места для проверки установки CancellationToken, эмпирическое правило - проверять его до и после любого await, и в вашем случае вы должны проверить его как в while, так и в for l oop.

...