Windows IoT BackgroundTask Async/Await
Я играю с Raspberry Pi 3 под управлением Windows IoT. Я подключил датчик DS18B20 и могу нормально с ним общаться через приложение UWP.
Теперь я хотел превратить это приложение в приложение BackgroundTask. Я использую этот код для комиксов OneWire
class WireSearchResult
{
public byte[] id = new byte[8];
public int lastForkPoint = 0;
}
public class OneWire
{
private SerialDevice serialPort = null;
DataWriter dataWriteObject = null;
DataReader dataReaderObject = null;
public async Task<string> GetFirstSerialPort()
{
try
{
string aqs = SerialDevice.GetDeviceSelector("UART0");
var dis = await DeviceInformation.FindAllAsync(aqs);
if (dis.Count > 0)
{
var deviceInfo = dis.First();
return deviceInfo.Id;
}
}
catch (Exception ex)
{
Debug.WriteLine("Unable to get serial device: " + ex.Message);
}
return null;
}
public void shutdown()
{
if (serialPort != null)
{
serialPort.Dispose();
serialPort = null;
}
}
async Task<bool> onewireReset(string deviceId)
{
try
{
if (serialPort != null)
serialPort.Dispose();
serialPort = await SerialDevice.FromIdAsync(deviceId);
// Configure serial settings
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = 9600;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
dataWriteObject = new DataWriter(serialPort.OutputStream);
dataWriteObject.WriteByte(0xF0);
await dataWriteObject.StoreAsync();
dataReaderObject = new DataReader(serialPort.InputStream);
await dataReaderObject.LoadAsync(1);
byte resp = dataReaderObject.ReadByte();
if (resp == 0xFF)
{
System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
return false;
}
else if (resp == 0xF0)
{
System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
return false;
}
else
{
//System.Diagnostics.Debug.WriteLine("Response: " + resp);
serialPort.Dispose();
serialPort = await SerialDevice.FromIdAsync(deviceId);
// Configure serial settings
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = 115200;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
dataWriteObject = new DataWriter(serialPort.OutputStream);
dataReaderObject = new DataReader(serialPort.InputStream);
return true;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
return false;
}
}
public async Task onewireWriteByte(byte b)
{
for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
{
// Run through the bits in the byte, extracting the
// LSB (bit 0) and sending it to the bus
await onewireBit((byte)(b & 0x01));
}
}
async Task<byte> onewireBit(byte b)
{
var bit = b > 0 ? 0xFF : 0x00;
dataWriteObject.WriteByte((byte)bit);
await dataWriteObject.StoreAsync();
await dataReaderObject.LoadAsync(1);
var data = dataReaderObject.ReadByte();
return (byte)(data & 0xFF);
}
async Task<byte> onewireReadByte()
{
byte b = 0;
for (byte i = 0; i < 8; i++)
{
// Build up byte bit by bit, LSB first
b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
}
// System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
return b;
}
public async Task<double> getTemperature(string deviceId)
{
double tempCelsius = -200;
if (await onewireReset(deviceId))
{
await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
await onewireWriteByte(0x44); //DS18B20 convert T command
// (initiate single temperature conversion)
// thermal data is stored in 2-byte temperature
// register in scratchpad memory
// Wait for at least 750ms for data to be collated
await Task.Delay(750);
// Get the data
await onewireReset(deviceId);
await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
// DS18B20 will transmit 9 bytes to master (us)
// starting with the LSB
byte tempLSB = await onewireReadByte(); //read lsb
byte tempMSB = await onewireReadByte(); //read msb
// Reset bus to stop sensor sending unwanted data
await onewireReset(deviceId);
// Log the Celsius temperature
tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it
System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
}
return tempCelsius;
}
}
И наконец StartupTask
public sealed class StartupTask : IBackgroundTask
{
private BackgroundTaskDeferral deferral;
private OneWire onewire;
private string deviceId = string.Empty;
private bool inprog = false;
private Timer timer;
public void Run(IBackgroundTaskInstance taskInstance)
{
deferral = taskInstance.GetDeferral();
onewire = new OneWire();
deviceId = await onewire.GetFirstSerialPort();
if(deviceId != null)
await onewire.getTemperature(deviceId));
BackgroundTaskDeferral.Complete();
}
}
У меня проблема в том, что когда я запускаю этот код, он висит на одной из строк, которая избавляется от SerialDevice
в OneWire
учебный класс.
Я читал в нескольких местах, что это связано с BackgroundTask и использованием Async/Await
4 ответа
Я использую тот же код onewire в фоновой задаче, чтобы общаться с датчиком DS18B20, и у меня было точно такое же поведение, как и у вас.
Я обнаружил, что если перед вызовом метода утилизации последовательного порта установить задержку в 100 миллисекунд, это сработает.
await Task.Delay(100)
Я пробовал менее 100 миллисекунд, но он просто зависал.
Этот ответ на вопросы о стеке потока сначала объясняет проблему с последовательными портами в.Net Framework. Почему Thread.Sleep() перед SerialPort.Open и Close?
Эта проблема не связана с BackgroundTask. Потому что ваш код вызывает ту же проблему в не BackgroundTask(приложение).
Его причина выглядит так, будто SerialPort несколько склонен к тупику.
Я обнаружил, что слишком много призваний onewireReset
метод, который закрывает и снова открывает SerialPort. Я не знаю, почему это должно быть сделано, но это вызывает проблему.
Таким образом, есть обходной путь: переписать логику связанной детали и убедиться, что открываем SerialPort в начале вашей программы и утилизируем его, когда он вам больше не нужен.
Вчера я только что столкнулся с той же самой проблемой (программа неожиданно зависает при удалении объекта SerialDevice в методе onewireReset(...)), и мне удалось ее решить.
Принцип решения: не выбрасывайте и не переоценивайте последовательный порт. Вместо этого приобретите порт один раз и перенастройте его по мере необходимости (= измените скорость передачи). Таким образом, зависание SerialDevice.Dispose() полностью исключается.
Примечание. Чтобы иметь возможность изменить скорость передачи данных, сначала необходимо отсоединить объекты DataReader и DataWriter от потока порта, иначе вы получите исключение. После изменения подключите новые объекты DataReader и DataWriter (не забудьте правильно утилизировать старые).
Модифицированный класс OneWire:
public class OneWire
{
private SerialDevice serialPort = null;
DataWriter dataWriteObject = null;
DataReader dataReaderObject = null;
public void shutdown()
{
if (serialPort != null)
{
serialPort.Dispose();
serialPort = null;
}
}
private async Task ReconfigurePort(uint baudRate, string deviceId)
{
if (serialPort == null)
{
serialPort = await SerialDevice.FromIdAsync(deviceId);
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = baudRate;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
dataWriteObject = new DataWriter(serialPort.OutputStream);
}
else
{
dataWriteObject.DetachStream();
dataWriteObject.DetachBuffer();
dataWriteObject.Dispose();
dataReaderObject.DetachStream();
dataReaderObject.Dispose();
serialPort.BaudRate = baudRate;
dataWriteObject = new DataWriter(serialPort.OutputStream);
}
}
async Task<bool> onewireReset(string deviceId)
{
try
{
await ReconfigurePort(9600, deviceId);
dataWriteObject.WriteByte(0xF0);
await dataWriteObject.StoreAsync();
dataReaderObject = new DataReader(serialPort.InputStream);
await dataReaderObject.LoadAsync(1);
byte resp = dataReaderObject.ReadByte();
if (resp == 0xFF)
{
//System.Diagnostics.Debug.WriteLine("Nothing connected to UART");
return false;
}
else if (resp == 0xF0)
{
//System.Diagnostics.Debug.WriteLine("No 1-wire devices are present");
return false;
}
else
{
//System.Diagnostics.Debug.WriteLine("Response: " + resp);
await ReconfigurePort(115200, deviceId);
dataReaderObject = new DataReader(serialPort.InputStream);
return true;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Exception: " + ex.Message);
return false;
}
}
public async Task onewireWriteByte(byte b)
{
for (byte i = 0; i < 8; i++, b = (byte)(b >> 1))
{
// Run through the bits in the byte, extracting the
// LSB (bit 0) and sending it to the bus
await onewireBit((byte)(b & 0x01));
}
}
async Task<byte> onewireBit(byte b)
{
var bit = b > 0 ? 0xFF : 0x00;
dataWriteObject.WriteByte((byte)bit);
await dataWriteObject.StoreAsync();
await dataReaderObject.LoadAsync(1);
var data = dataReaderObject.ReadByte();
return (byte)(data & 0xFF);
}
async Task<byte> onewireReadByte()
{
byte b = 0;
for (byte i = 0; i < 8; i++)
{
// Build up byte bit by bit, LSB first
b = (byte)((b >> 1) + 0x80 * await onewireBit(1));
}
//System.Diagnostics.Debug.WriteLine("onewireReadByte result: " + b);
return b;
}
public async Task<double> getTemperature(string deviceId)
{
double tempCelsius = -200;
if (await onewireReset(deviceId))
{
await onewireWriteByte(0xCC); //1-Wire SKIP ROM command (ignore device id)
await onewireWriteByte(0x44); //DS18B20 convert T command
// (initiate single temperature conversion)
// thermal data is stored in 2-byte temperature
// register in scratchpad memory
// Wait for at least 750ms for data to be collated
//await Task.Delay(250);
// Get the data
await onewireReset(deviceId);
await onewireWriteByte(0xCC); //1-Wire Skip ROM command (ignore device id)
await onewireWriteByte(0xBE); //DS18B20 read scratchpad command
// DS18B20 will transmit 9 bytes to master (us)
// starting with the LSB
byte tempLSB = await onewireReadByte(); //read lsb
byte tempMSB = await onewireReadByte(); //read msb
// Reset bus to stop sensor sending unwanted data
await onewireReset(deviceId);
// Log the Celsius temperature
tempCelsius = ((tempMSB * 256) + tempLSB) / 16.0;
var temp2 = ((tempMSB << 8) + tempLSB) * 0.0625; //just another way of calculating it
//System.Diagnostics.Debug.WriteLine("Temperature: " + tempCelsius + " degrees C " + temp2);
}
return tempCelsius;
}
}
Я столкнулся с той же проблемой с зависанием SerialDevice.Dispose(). Ожидание Task.Delay(100), похоже, решило проблему. Однако я не сторонник слепого добавления задержек по непонятным мне причинам. После некоторого тестирования кажется, что то же самое можно сделать, запустив dispose для своей собственной задачи.
//Dispose of the serial port object
System.Diagnostics.Debug.WriteLine("Disposing");
mDataWriter.DetachStream();
mDataWriter.Dispose();
mDataWriter = null;
mDataReader.DetachStream();
mDataReader.Dispose();
mDataReader = null;
try
{
CancellationTokenSource DisposePortCancellationTokenSource = new CancellationTokenSource(200);
await Task.Run(() => mSerialDevice.Dispose(), DisposePortCancellationTokenSource.Token);
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine("Error disposing of serial device. Device may need to be manually unplugged and re-plugged in");
}
mSerialDevice = null;
System.Diagnostics.Debug.WriteLine("Disposed");