2017-04-21 2 views
0
  • 環境
    • のWindows 7
    • のVisual Studio Professionalの2013
    • C#

私は、デバイスと通信アプリ、安定化電源に取り組んでいます出力電圧を制御して、出力電圧を設定または取得します。 たとえば、現在の電圧が0Vで、デバイスに10Vが設定されている場合、出力電圧 を入力電圧10Vに変更しようとします。デバイスから電圧を読み取った場合、0V、1V、2V、... 8V、9V、10Vのように徐々に時間の経過とともに上昇することがわかります。 アプリはチャートの電圧の時間経過も表示します。1つのワーカースレッドで複数の非同期タスクを実行するにはどうすればよいですか?

私は関数を実装するためのコードを書いています。コードに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(); 
} 

ただし、1つの問題が発生しました。 電圧を取得するコマンドがデバイスに送信され、プログラムが応答を待っているときに、デバイスに電圧を設定する新しいコマンドが送信された場合は、 デバイスはメッセージを正しく処理できず、 バイト配列。それはデバイスの仕様のように思えるので、変更することはできません。

この問題を回避するには、非同期のコマンドを送信するメソッドを1つのスレッドで実行し、 を1つずつ処理する必要があります。しかし、StackOverflowでのグーグルと検索では、私には有用な情報はありませんでした。 これを達成するにはどうすればよいですか?前もって感謝します。

+0

あなたは単純なシングルユーザーデスクトップアプリケーションを持っているように見えるので、フェッチしようとしている間に電圧を設定するユーザーの機能を削除してみませんか? – JSteward

+0

実際には、このアプリの機能の1つは、入力設定点にどれくらい早く応答できるかを測定することです。そのため、測定中に設定値をデバイスに送信する必要があります。申し訳ありませんが、私は説明している必要があります。 – 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 = Task_Run()=> Get_voltage_from_device());を待たずに 'Exec_command'メソッド内の文全体を囲むべきです。 – dixhom

+0

とても助かりました。すべてのクレジットはスティーブンに行く! =) – BlueStrat

関連する問題