2017-04-21 71 views
0
  • 环境
    • 的Windows 7
    • 的Visual Studio专业2013
    • C#

我正在与一个设备进行通信的应用程序,稳压电源,它控制输出电压以设置或获取电压。 例如,如果当前电压为0V且器件设置为10V,则它会尝试将输出电压 更改为输入电压10V。如果从设备读取电压,则可以看到它随着时间逐渐上升,如0V,1V,2V,... 8V,9V,10V。该应用程序还显示图表上电压的时间过程。如何在一个工作线程中运行多个异步任务?

我写了一个代码来实现这些功能。代码中有一个while循环来获取电压并连续显示在图表上 因此我使用异步/等待的异步编程来提高响应速度。这里是实际代码的简化版本。

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上搜索和搜索没有给我有用的信息。 我该怎么做才能做到这一点?提前致谢。

+0

既然看起来你有一个简单的单用户桌面应用程序,为什么不删除用户设置电压,而你试图获取它的能力? – JSteward

+0

实际上,这个应用程序的功能之一将是测量它可以响应输入设置点的速度。所以设定点应在测量过程中发送到设备。对不起,我应该解释一下。 – dixhom

回答

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

为方便起见,这里是一个完整的实现我设计了强烈根据斯蒂芬的文章(我拿的内置awaitable 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; 
     } 
    } 
} 
+0

感谢您的精彩课程!让我纠正一件事。 '使用(var releaser = await m_lock.LockAsync())'不应该包含'double voltage = await Task.Run(()=> Get_voltage_from_device());',而是'Exec_command'方法中的整个语句。 – dixhom

+0

非常高兴它有帮助。所有功劳都归Stephen所有! =) – BlueStrat

相关问题