我正在开发一款与设备通信的应用程序,这是一种稳压电源,可以控制输出电压来设置电压或从中获取电压。 例如,如果当前电压为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上搜索和搜索没有给我任何有用的信息。 我该怎么做才能做到这一点?提前谢谢。
答案 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;
}
}
}