高性能异步监控任务

时间:2016-05-15 02:08:45

标签: c# multithreading asynchronous async-await

我有几百台设备,我需要每隔5秒检查一下它们的状态。

我正在使用的API包含一个调用dll并返回单个设备状态的阻塞函数

string status = ReadStatus(int deviceID); // waits here until the status is returned

上述功能通常会在几毫秒内返回状态,但是在某些情况下我可能无法恢复状态一秒或更长时间!或者更糟糕的是,一台设备可能根本没有响应。

因此,我需要引入一种异步性的形式,以确保一个没有响应的设备不会阻止所有其他设备被监控。

我目前的方法如下

// triggers every 5 sec
public MonitorDevices_ElapsedInterval(object sender, ElapsedEventArgs elapsedEventArgs)
{       
   foreach (var device in lstDevices) // several hundred devices in the list
   {
       var task = device.ReadStatusAsync(device.ID, cts.Token);
       tasks.Add(task);
    }

   // await all tasks finished, or timeout after 4900ms
   await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(4900, cts.Token));
   cts.Cancel();

   var devicesThatResponded = tasks.Where(t => t.Status == TaskStatus.RanToCompletion)
        .Select(t => t.GetAwaiter().GetResult())
        .ToList();
}

以下在Device类

public async Task ReadStatusAsync(int deviceID, CancellationToken tk)
{  
    await Task.Delay(50, tk);   
    // calls the dll to return the status. Blocks until the status is return     
    Status = ReadStatus(deviceID);
}

我的代码存在一些问题

  1. foreach循环同时激活几百个任务,来自Task.Delay的回调由来自线程池的线程提供服务,每个任务花费几毫秒。
  2. 我认为这是一个巨大的潜在瓶颈。还有更好的方法吗?

    这可能类似于Stephen Cleary在这里所评论的内容,但他没有提供替代What it costs to use Task.Delay()?

    1. 如果ReadStatus无法返回,我正在尝试使用取消令牌来取消那里等待响应的线程...这似乎不起作用。

      等待Task.Delay(50,tk)
      Thread.Sleep(100000)//模拟设备无响应

    2. 我仍然有大约20个工作线程存活(即使我期待cts.Cancel()杀死它们。

2 个答案:

答案 0 :(得分:5)

  

foreach循环同时发射几百个任务

由于ReadStatus是同步的(我假设你不能改变它),并且因为每个人都需要独立,因为它们可以阻止调用线程,所以你有数百个任务。这已经是最有效的方式了。

  

有没有更好的方法?

如果每隔5秒读取一次设备,那么每台设备都有自己的计时器可能会更好。经过几个周期后,他们应该“均匀”。

  

await Task.Delay(50,tk);

我不建议使用Task.Delay来“蹦床”非异步代码。如果您希望在线程池上运行代码,只需将其包装在Task.Run

foreach (var device in lstDevices) // several hundred devices in the list
{
  var task = Task.Run(() => device.ReadStatus(device.ID, cts.Token));
  tasks.Add(task);
}
  

我正在尝试使用取消令牌取消坐在那里等待响应的线程...这似乎不起作用。

取消令牌不会杀死线程。如果ReadStatus观察到其取消令牌,那么它应该取消;如果没有,那么你无能为力。

线程池线程应该不被终止;这可以减少计时器下次触发时的线程搅动。

答案 1 :(得分:0)

正如您在this Microsoft example page of a cancellation token中看到的那样,doWork方法正在检查每个循环的取消。因此,循环必须再次开始取消。在您的情况下,当您模拟长任务时,它在运行时从不检查取消。

来自How do I cancel non-cancelable async operations?,它最后说:"那么,你可以取消不可取消的操作吗?不可以取消对不可取消的操作的等待吗?当然......当你这么做时要非常小心。"。所以它回答说我们无法取消它。

我建议使用ThreadPool的线程,你需要每个线程的开始时间,你有一个更高优先级的线程,看看其他线程是否绕过了他们的最大允许时间。如果是,Thread.Interrupt()