Windows 窗体 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("rn");
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("rn");
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);
}
}

您需要传入取消令牌。 在此代码之外的某个地方,您需要创建一个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的地方是在Task.WhenyAny之后或循环间隔之后,但如果已请求取消,则无需开始ConnectAsync()

您还应该将取消令牌传递到循环间隔,否则您最终可能会在表单关闭之前等待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));

最后,您需要处理CancelTokenSource,我想您会在类似MainForm_Closed()函数之类的函数中执行此操作吗?

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

剩下唯一要做的就是确定何时要触发取消请求,根据您在单击表单关闭按钮时所说的要执行此操作的内容,因此:

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

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

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

最新更新