如何在一个工作线程中运行多个异步任务?
问题描述:
- 环境
- 的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上搜索和搜索没有给我有用的信息。 我该怎么做才能做到这一点?提前致谢。
答
我会推荐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;
}
}
}
既然看起来你有一个简单的单用户桌面应用程序,为什么不删除用户设置电压,而你试图获取它的能力? – JSteward
实际上,这个应用程序的功能之一将是测量它可以响应输入设置点的速度。所以设定点应在测量过程中发送到设备。对不起,我应该解释一下。 – dixhom