如何在一个工作线程中运行多个异步任务?

时间:2017-04-21 23:55:17

标签: c# multithreading asynchronous serial-port async-await

  • 环境
    • Windows 7
    • Visual Studio Professional 2013
    • C#

我正在开发一款与设备通信的应用程序,这是一种稳压电源,可以控制输出电压来设置电压或从中获取电压。 例如,如果当前电压为0V且器件设置为10V,它将尝试改变输出电压 输入一,10V。如果你从设备读取电压,那么你可以看到它随着时间的推移逐渐上升, 如0V,1V,2V,...... 8V,9V,10V。该应用程序还显示图表上电压的时间进程。

我编写了一个代码来实现这些功能。代码中有一个while循环来获取电压并连续显示在图表上 所以我使用异步编程和async / await来增强响应能力。这是实际代码的简化版本。

private bool loop_flag = true;
private System.IO.Ports.SerialPort sp = new SerialPort();
private async Task measurement()
{
    Open_serial_port();

    loop_flag = true;
    var sw = new Stopwatch();
    while (loop_flag)
    {
        double voltage = await Task.Run(() => Get_voltage_from_device());
        Update_chart(sw.ElapsedMilliseconds, voltage); // this updates a chart control showing the time course of the value.
    }
    sw.Stop();
    Close_serial_port();
}

private double Get_voltage_from_device()
{
    return Parse_bytes_into_voltage(Exec_command("get voltage"));
}

private void button_set_voltage_Click(object sender, EventArgs e)
{
    Exec_command("set voltage " + textBox_voltage.Text);
}

private void button_stop_Click(object sender, EventArgs e)
{
    loop_flag = false;
}

private byte[] Exec_command(string command)
{
    sp.DiscardInBuffer();
    sp.DiscardOutBuffer();
    Send_command_using_serialport(command); // uses SerialPort.Write() method

    var received_data_raw = new List<byte>();
    var sw = new Stopwatch();
    sw.Start();
    // since data from the device may be received in several parts 
    // getting data repeatedly using while loop is necessary
    // this loop usually takes about 20 msec to finish
    while (true) 
    {
        if (sw.ElapsedMilliseconds > 1000) // 1000 can be anything not too large or small.
        {
            throw new TimeoutException();
        }

        if (sp.BytesToRead == 0) // the buffer is often empty
            continue;

        while (sp.BytesToRead > 0)
        {
            received_data_raw.Add((byte)sp.ReadByte());
        }

        if (received_data_raw.Count == 1 && received_data_raw.ToArray()[0] == 0xFF) // 0xFF means the voltage was set successfully.
            break;

        if (received_data_raw.Count == 2) // all the data for voltage were received
            break;
    }
    sw.Stop();

    return received_data_raw.ToArray();
}

但是,我遇到了一个问题。 当获取电压的命令被发送到设备并且程序正在等待回复时, 如果发送了设置电压的新命令,则设备无法正确处理消息并发回 一个乱码字节数组。它似乎是设备的规格,因此无法更改。

为了避免这个问题,发送异步命令的方法应该在一个线程中运行 一个接一个地处理。但是,在StackOverflow上搜索和搜索没有给我任何有用的信息。 我该怎么做才能做到这一点?提前谢谢。

1 个答案:

答案 0 :(得分:2)

我会推荐 Stephen Toub 优秀的AsyncLock解决方案。 它为您提供了与传统锁类似的语义,但是,想要访问共享资源的调用线程(在您的情况下是轮询设备的代码)将不会阻止锁定是否已被占用,而不是阻塞它们将产生执行和当释放锁时,将被延续唤醒

这是一个如何运作的例子;

private readonly AsyncLock m_lock = new AsyncLock(); 
… 
using(var releaser = await m_lock.LockAsync()) 
{ 
   … // only a single thread can run this code at a time
   double voltage = await Task.Run(() => Get_voltage_from_device());
}

为了您的方便,这是我在Stephen的文章中强烈设计的完整实现(我利用内置的等待的SemaphoreSlim,我认为在撰写文章时它并不存在)

   /// <summary>
   /// An async mutex.  When awaiting for the lock to be released, instead of blocking the calling thread,
   /// a continuation will resume execution
   /// </summary>

   ///<example>
   ///   using( await _asyncLock.LockAsync() ) {
   ///      use shared resource
   ///   }
   /// </example>

   /// Original author:
   /// Stephen Toub
   /// https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/

   public class AsyncLock {

       public struct Releaser : IDisposable {
          private readonly AsyncLock _toRelease;
          internal Releaser(AsyncLock toRelease) {
             _toRelease = toRelease;
          }
          public void Dispose() {
             _toRelease._semaphore.Release();
          }
       }

       private SemaphoreSlim _semaphore;
       private Task<Releaser> _releaserTask;

       public AsyncLock() {
          _semaphore = new SemaphoreSlim(1, 1);
          _releaserTask = Task.FromResult(new Releaser(this));
       }

       public Task<Releaser> LockAsync() {
          var wait = _semaphore.WaitAsync();
          if( wait.IsCompleted )
             return _releaserTask;
          var continuation = wait.ContinueWith( (_, state) => new Releaser((AsyncLock)state),
                                                this, 
                                                CancellationToken.None, 
                                                TaskContinuationOptions.ExecuteSynchronously, 
                                                TaskScheduler.Default);

          return continuation;
       }



       public Releaser Lock() {
          _semaphore.Wait();
          return _releaserTask.Result;
       }
    }
}