首页 > 解决方案 > Windows Forms C#的异步函数调用

问题描述

我有一个异步函数。第一次显示表单时只调用一次。当我打开程序时,我的功能应该异步 ping 设备。但事实证明,当您关闭子窗体时,会启动另一个民意调查。告诉我错误可能在哪里。

函数调用(我试图在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);
    }
}

标签: c#winformsasync-await

解决方案


您需要传递一个取消令牌。在此代码之外的某个地方,您需要创建一个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);
}

在您的启动循环中,您需要监视取消令牌:

因为您使用延迟来超时,所以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将是在 Loop 间隔之后或之后立即出现,但如果已请求取消,则Task.WhenyAny没有必要开始。ConnectAsync()

您还应该将 CancellationToken 传递给循环间隔,否则您可能会interval在表单关闭之前等待:

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

因为无论如何您都将继续并在注册取消时退出,您可以避免编写一个单独的 try/catch 什么都不做并等待这样的间隔,它几乎可以肯定效率较低,但它更干净。

// 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();

这将导致 CancellationToken 转换为取消状态,您的Start()例程将看到并退出。

在您的代码中,没有一个地方可以检查是否设置了 CancellationToken,经验法则是在任何之前和之后检查它,在您的情况下,您应该在循环和循环await中检查它。whilefor


推荐阅读