Task and Background Worker thread safety, and Task Cancellation [duplicate] - c#

This question already has answers here:
From Eric Lippert's blog: "don't close over the loop variable" [duplicate]
(4 answers)
Captured variable in a loop in C#
(10 answers)
Closed 1 year ago.
This post was edited and submitted for review 1 year ago and failed to reopen the post:
Original close reason(s) were not resolved
I've made a real simple console app that I am using to break down a problem I am having on a larger application. I feel like I must have missed something in thread 101 class but I'm at a loss of what it is. Basically, the whole premise is to have a BackgroundWorker check on a timer a collection and then if another collection doesn't contain something from the first collection, start a new Task. If any anytime the Task isn't in running or created status, I want to cancel the task and create a new one.
The two behaviors I am noticing is one when there is an exception inside of the task, I am getting errors for Car Models that don't have an error (even though the task is new), and if I use a for loop my i iteration goes to 5 and the collection never has more than 4. I've also noticed that all 4 make an error at the same time, but I know there are some weird things with Random but I'm just not sure if that's the issue at this point.
If you want to see the for iteration go to 5, just use Y for the console readline, but this only happens in debug, running the application this doesn't seem to occur. The next thing I want to make sure of is that I am canceling the task properly.
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.ComponentModel;
using System.IO;
namespace ThreadTest
{
class Program
{
private static BackgroundWorker _backgroundWorker;
private static void _EstablishBackgroundWorker()
{
_backgroundWorker = new BackgroundWorker();
if (UseFor)
_backgroundWorker.DoWork += _UpdateCarsFor;
else
_backgroundWorker.DoWork += _UpdateCars;
Timer timer = new Timer(10000);
timer.Elapsed += _Timer_Elapsed;
timer.Start();
}
private static void _Timer_Elapsed(object sender, ElapsedEventArgs e)
{
while (_backgroundWorker.IsBusy)
{
}
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private static void _UpdateCarsFor(object sender, DoWorkEventArgs e)
{
string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
for (int i = 0; i < _cars.Count; i++)
{
if (_cars[i].Task != null && _cars[i].Task.Status != TaskStatus.Running && _cars[i].Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { _cars[i].Model }");
_cars[i].tokenSource.Cancel();
_cars[i].RefreshToken();
_cars[i].Task = null;
}
if (_cars[i].Task == null)
{
_cars[i].Task = new Task(() => new Manufacture.Build().MakeCar(_cars[i].Model), _cars[i].cancellationToken);
_cars[i].Task.Start();
}
}
}
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}
private static List<Model.Car> _cars { get; set; }
private static bool UseFor { get; set; }
static void Main(string[] args)
{
_cars = new List<Model.Car>();
Console.WriteLine("Use for iteration? y/n");
var result = Console.ReadLine();
if (result.ToUpper() == "Y")
UseFor = true;
_EstablishBackgroundWorker();
Console.ReadLine();
}
}
}
Car.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest.Model
{
class Car
{
public Car()
{
RefreshToken();
}
public string Model { get; set; }
public Task Task { get; set; }
public CancellationToken cancellationToken { get; set; }
public CancellationTokenSource tokenSource { get; set; }
public void RefreshToken()
{
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
}
}
}
Build.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadTest.Manufacture
{
class Build
{
public void MakeCar(string car)
{
try
{
while (true)
{
Random r = new Random();
int chaos = r.Next(0, 100);
Console.WriteLine($"Building {car}");
Thread.Sleep(2000);
if (chaos >= 90) throw new Exception($"Something went wrong with {car}");
}
}
catch(Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
throw;
}
}
}
}
Here is an example of all threads erroring at the same time, but even if i turn random down to a 1% chance this still happens.
All errors
In the comments with Henk, the mention of closing over the loop variable I don't believe applies because this method present a foreach and the issue of all threads erroring at the same time occurs, and the question of property canceling a task still remains.
private static void _UpdateCars(object sender, DoWorkEventArgs e)
{
//string[] myCars = File.ReadAllLines(#"C:\cars\mycarfile.txt");
string[] myCars = new string[] { "F150", "RAM", "M3", "NSX" };
for (int i = 0; i < myCars.Length; i++)
if (!_cars.Where(x => x.Model == myCars[i].ToUpper()).Any())
_cars.Add(new Model.Car() { Model = myCars[i].ToUpper() });
foreach (var car in _cars)
{
if (car.Task != null && car.Task.Status != TaskStatus.Running && car.Task.Status != TaskStatus.Created)
{
Console.WriteLine($"Making new task for { car.Model }");
car.tokenSource.Cancel();
car.RefreshToken();
car.Task = null;
}
if (car.Task == null)
{
car.Task = new Task(() => new Manufacture.Build().MakeCar(car.Model), car.cancellationToken);
car.Task.Start();
}
}
}

Related

C# Previous object is overwritted when adding new object to list

When I am adding new Timer to _timers it is overwriting previous one.
Console.WriteLine(_timers.Count()); is always writing 1
string.Join<string>(", ", _timers.Select(x => x.Uuid)); is returning empty string.
Here is my code:
[Group("timer")]
public class TimerHandler : ModuleBase<SocketCommandContext>
{
private List<Timer> _timers = new List<Timer>();
[Command("new")]
public async Task NewTimer(string content, [Remainder] int delay)
{
Timer timer = new Timer(content, delay, Context.Channel);
_timers.Add(timer);
Console.WriteLine(_timers.Count());
await ReplyAsync("created new timer with id: " + timer.Uuid);
}
[Command("list")]
public async Task ListTimer()
{
string reply;
string reply = string.Join<string>(", ", _timers.Select(x => x.Uuid));
await ReplyAsync(reply);
}
}
At times like these, what I try to do is create a minimum reproducible sample that removes anything unrelated to the problem. You could use a quick console app like I did here or a Unit Test. What this seems to show is that your code works as far as is shown:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
namespace timer_list
{
class Program
{
static void Main(string[] args)
{
runAsync();
Console.ReadKey();
}
static async void runAsync()
{
var timerHandler = new TimerHandler();
for (int i = 0; i < 5; i++)
{
await timerHandler.NewTimer("unused");
}
}
public class TimerHandler // : ModuleBase<SocketCommandContext>
{
private List<Timer> _timers = new List<Timer>();
public async Task NewTimer(string content)
{
Timer timer = new Timer();
_timers.Add(timer);
Console.WriteLine(_timers.Count());
await Task.Delay(1);
}
public async Task ListTimer()
{
await Task.Delay(10);
}
}
}
}
This makes me wonder if you're making a new instance of TimerHandler every time you use it. If this is the case, try changing:
private List<Timer> _timers = new List<Timer>()
to
private static List<Timer> _timers = new List<Timer>()
and see if that helps!

UWP Bluetooth Low Energy Application Disconnects Early

So I am designing an application for windows laptops to connect to a custom designed pressure sensor. The application pairs to the device and then receives notifications from the device every 10 ms. Then for some reason the communication stops. I know it is a problem with my application and not with the device, because when I connect to my phone, I do not have this problem.
Here is the main page where I create the devicewatcher and discover the device:
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private DeviceWatcher deviceWatcher;
private ObservableCollection<DeviceInformation> deviceList = new ObservableCollection<DeviceInformation>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = deviceList;
deviceListView.ItemsSource = deviceList;
deviceWatcher = DeviceInformation.CreateWatcher(
"System.ItemNameDisplay:~~\"Button\"",
new string[] {
"System.Devices.Aep.DeviceAddress",
"System.Devices.Aep.IsConnected" },
DeviceInformationKind.AssociationEndpoint);
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Removed += DeviceWatcher_Removed;
deviceWatcher.Start();
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Collapsed;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
deviceWatcher.Stop();
base.OnNavigatedFrom(e);
}
private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
var toRemove = (from a in deviceList where a.Id == args.Id select a).FirstOrDefault();
if (toRemove != null)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Remove(toRemove); });
}
}
private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Add(args); });
}
private void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
this.Frame.Navigate(typeof(DevicePage), e.ClickedItem);
}
}
}'
This next code is the page where the pressure sensor is connected to and where data is read from the device.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Popups;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class DevicePage : Page
{
private DeviceInformation device { get; set; }
private PressureSensor pSensor { get; set; }
public static DateTime startTime { get; set; }
public ObservableCollection<DataPoint> PressureData = new ObservableCollection<DataPoint>();
public static ObservableCollection<DataPoint> inbetween;
private static TextBox txtP;
private BluetoothLEDevice leDevice;
private DispatcherTimer timer = new DispatcherTimer();
private int packetNum = 0;
public DevicePage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().BackRequested += DevicePage_BackRequested;
txtP = txtValue1;
inbetween = PressureData;
}
public static void ChangeText(string text)
{
txtP.Text = text;
}
private async void InitializePressureSensor(GattDeviceService service)
{
pSensor = new PressureSensor(service, SensorUUIDs.PressureSensorUuid);
await pSensor.EnableNotifications();
btnStart.IsEnabled = true;
}
private async void StartRecievingData()
{
try
{
leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
leDevice.BluetoothAddress.ToString("X") + "\")";
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
foreach (var service in services.Services)
{
if (service.Uuid.ToString() == SensorUUIDs.ButtonSensorServiceUuid)
{
InitializePressureSensor(service);
}
}
timer.Interval = new TimeSpan(0, 0, 0, 0, 1);
timer.Tick += Timer_Tick1;
startTime = DateTime.Now;
timer.Start();
}
catch (Exception ex)
{
var messageDialog = new MessageDialog("An error has occured Please try again. \n" + ex.Message, "Error!");
}
}
public async void UpdateAllData()
{
while (pSensor != null && pSensor.MorePacketsAvailable)
{
int[] values = await pSensor.GetPressure();
int packetNumber = values[0];
if (packetNumber > packetNum)
{
packetNum = packetNumber;
txtValue1.Text = Convert.ToString(values[1]);
txtValue2.Text = Convert.ToString(values[5]);
for (int i = 1; i < 5; i++)
{
PressureData.Add(new DataPoint(DateTime.Now - startTime, packetNumber, ((i-1)*2.5 + 10*packetNumber), values[i], values[i + 4]));
}
}
}
}
private void Timer_Tick1(object sender, object e)
{
UpdateAllData();
}
private async void PairToDevice()
{
if (device.Pairing.CanPair)
{
var customPairing = device.Pairing.Custom;
customPairing.PairingRequested += CustomPairing_PairingRequested;
var result = await customPairing.PairAsync(DevicePairingKinds.ConfirmOnly);
customPairing.PairingRequested -= CustomPairing_PairingRequested;
if ((result.Status == DevicePairingResultStatus.Paired) || (result.Status == DevicePairingResultStatus.AlreadyPaired))
{
/*while (device.Pairing.IsPaired == false)
{
device = await DeviceInformation.CreateFromIdAsync(device.Id);
}*/
StartRecievingData();
}
}
else if (device.Pairing.IsPaired)
{
StartRecievingData();
}
}
private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
args.Accept();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
btnSave.Content = "Save";
btnStop.IsEnabled = false;
btnStart.IsEnabled = false;
this.DataContext = PressureData;
device = (DeviceInformation)e.Parameter;
PairToDevice();
//StartRecievingData();
base.OnNavigatedTo(e);
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Visible;
}
}
private void DevicePage_BackRequested(object sender, BackRequestedEventArgs eventArgs)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
return;
}
// Navigate back if possible, and if the event has already been handled
if (rootFrame.CanGoBack && eventArgs.Handled ==false)
{
eventArgs.Handled = true;
rootFrame.GoBack();
}
}
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
var picker = new FileSavePicker();
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeChoices.Add("CSV", new List<string>() { ".csv" });
StorageFile file = await picker.PickSaveFileAsync();
if (file != null)
{
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
{
using (var writer = new DataWriter(outputStream))
{
foreach (DataPoint p in PressureData)
{
string text = p.TimeStamp.ToString() + "," + p.PacketNumber.ToString() + "," + p.InternalTimestamp.ToString() + "," + p.PressureValue1.ToString() + "," + p.PressureValue2.ToString() + "\n";
writer.WriteString(text);
}
await writer.StoreAsync();
await writer.FlushAsync();
}
}
stream.Dispose();
}
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
if (pSensor != null)
{
btnStop.IsEnabled = true;
btnStart.IsEnabled = false;
startTime = DateTime.Now;
if (pSensor != null)
{
await pSensor.BeginCollecting();
}
}
}
private async void btnStop_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
if (pSensor != null)
{
await pSensor.StopCollecting();
}
}
}
}
Here is where I define my SensorBase and PressureSensor class that handles the device connection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;
using Windows.Devices.Enumeration;
namespace BLEInterfaceTest
{
public static class SensorUUIDs
{
private static readonly string _packetUuid = "0000a043-0000-1000-8000-00805f9b34fb";
private static readonly string _buttonSensorServiceUuid = "0000a042-0000-1000-8000-00805f9b34fb";
private static readonly string _sensorStateUuid = "0000a044-0000-1000-8000-00805f9b34fb";
public static string PressureSensorUuid
{
get { return _packetUuid; }
}
public static string ButtonSensorServiceUuid
{
get { return _buttonSensorServiceUuid; }
}
public static string SensorStateUuid
{
get { return _sensorStateUuid; }
}
}
public class SensorBase : IDisposable
{
protected GattDeviceService deviceService;
protected string sensorDataUuid;
protected Queue<byte[]> fifoBuffer;
protected bool isNotificationSupported = false;
public bool newData = false;
private GattCharacteristic dataCharacteristic;
public SensorBase(GattDeviceService dataService, string sensorDataUuid)
{
this.deviceService = dataService;
this.sensorDataUuid = sensorDataUuid;
fifoBuffer = new Queue<byte[]>(20);
}
public bool MorePacketsAvailable
{
get
{
if (fifoBuffer.Count > 0)
{
return true;
}
else
{
return false;
}
}
}
public virtual async Task EnableNotifications()
{
GattCharacteristicsResult result = await deviceService.GetCharacteristicsAsync();
foreach (var test in result.Characteristics)
{
string t = test.Uuid.ToString();
}
isNotificationSupported = true;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged += dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
var currentDescriptorValue = await dataCharacteristic.ReadClientCharacteristicConfigurationDescriptorAsync();
if (currentDescriptorValue.Status != GattCommunicationStatus.Success
|| currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify)
{
GattCommunicationStatus status2 = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
}
}
public virtual async Task DisableNotifications()
{
newData = false;
isNotificationSupported = false;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged -= dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
}
protected async Task<byte[]> ReadValue()
{
if (!isNotificationSupported)
{
if (dataCharacteristic == null)
{
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
}
GattReadResult readResult = await dataCharacteristic.ReadValueAsync();
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
fifoBuffer.Enqueue(data);
}
return fifoBuffer.Dequeue();
}
protected async Task WriteByteArray(string characteristicUuid, byte[] value)
{
GattCharacteristic writeCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(characteristicUuid))).Characteristics[0];
var writer = new DataWriter();
writer.WriteBytes(value);
var res = await writeCharacteristic.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
}
private void dataCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
byte[] data = new byte[args.CharacteristicValue.Length];
DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
fifoBuffer.Enqueue(data);
newData = true;
}
public async void Dispose()
{
await DisableNotifications();
}
}
public class PressureSensor: SensorBase
{
public PressureSensor(GattDeviceService dataService, string sensorDataUuid)
: base(dataService, sensorDataUuid)
{
}
public async Task BeginCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
public async Task<int[]> GetPressure()
{
byte[] data = await ReadValue();
if (data != null)
{
int[] values = new int[9];
values[0] = (int)BitConverter.ToInt32(data, 0);
for (int i = 1; i < values.Length; i++)
{
values[i] = (int)BitConverter.ToInt16(data, 2 * i + 2);
}
return values;
}
else
{
return new int[] { 0 };
}
}
public async Task StopCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x00 });
}
}
}
Here is the DataPoint Class that I use to organize the data received from the pressure sensor:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BLEInterfaceTest
{
public class DataPoint : INotifyPropertyChanged
{
private TimeSpan _timestamp;
private int _packetNumber;
private double _internalTimestamp;
private int _pressure1;
private int _pressure2;
public event PropertyChangedEventHandler PropertyChanged;
public TimeSpan TimeStamp
{
get { return _timestamp; }
set
{
_timestamp = value;
this.NotifyPropertyChanged();
}
}
public int PacketNumber
{
get { return _packetNumber; }
set
{
_packetNumber = value;
this.NotifyPropertyChanged();
}
}
public double InternalTimestamp
{
get { return _internalTimestamp; }
set
{
_internalTimestamp = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue1
{
get { return _pressure1; }
set
{
_pressure1 = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue2
{
get { return _pressure2; }
set
{
_pressure2 = value;
this.NotifyPropertyChanged();
}
}
public DataPoint(TimeSpan time,int packetNumber, double internalTimestamp, int pressure1, int pressure2)
{
_timestamp = time;
_packetNumber = packetNumber;
_internalTimestamp = internalTimestamp;
_pressure1 = pressure1;
_pressure2 = pressure2;
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (!string.IsNullOrEmpty(propertyName))
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I have researched this extensively, and all I could find was help on how to initiate a disconnection. I have the opposite problem. One page I found stated that the problem might be caused by the device not properly storing the bonding state, but I have checked that and I did initialize the device to save the bonding state.
Interestingly if I do not pair the device to the computer before trying to read information from it then I do not have the problem. The connection never randomly stops. But when I do this, the computer does not receive every packet of data sent from the sensor device. It will receive one or two packets and then skip five or six packets. If I pair the device then I will receive every packet but the connection will randomly cut off.
So my question is two fold, I guess. How do I stop the connection from cutting off when the device is paired? Or alternatively, is there a way to allow the application to receive every packet of data when it is not paired?
UPDATE
I realized I should include more information on my sensor peripheral in case the error is in that code. I am currently designing a rapid prototyping of this sensor before I move on to designing the embedded version. To do this, I am using the BLE Nano 1 from RedBearLabs as a user friendly prototype. I am programing this device with the online MBED compiler. I have included the nRF51822 and BLE_API libraries to handle the bluetooth low energy communication.
UPDATE 2
So after more research into what is causing the problem, I have found that the disconnection occurs when a connection interval and a generation 2 garbage collection occur at the same time. In UWP the garbage collector can pause the UI Thread for generation 2 collections. (see here)
My thought is that if the thread is paused at the beginning of a connection interval, then the central is not able to initiate the connection with the peripheral and the peripheral therefore thinks the client is no longer listening (see more about how BLE connections work).
I discovered this by finding out exactly what is necessary to get the connection back once it has randomly stopped. I started with the entire connection process and reduced it down to this:
public async Task ReconnectDevice()
{
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
Because my BluetoothLEDevice, GattService, and GattCharacteristic objects are not disposed, all I need to do is resubscribe to notifications and write a 1 to the device so that it begins collecting data again.
I have reduced my memory allocations in my application significantly since discovering this, and the time for a gen2 collection has decreased to an average of 5 ms. Also, the amount of time before the connection disconnects has increased to around 4-5 sec.
UWP has a GattCharacteristicNotificationTrigger for receiving notifications in a BackgroundTask, but I have never had much success at incorporating background tasks in UWP.
I think I will try next to incorporate the windows.devices into a WPF application where I think I will have a better chance at getting it working.
So, after a while of trying different ideas I have finally stumbled across a solution to my problem. I had to make 2 changes:
Used the unpaired connection instead of the paired connection. This solved the problem of the connection dropping suddenly.
Increased the connection interval to 40 ms. For some reason when I did this, I received all of the data and no longer had any problems. Anything below 40 ms causes information to be lost when communicating to a Windows device (I had to make this change on the C code running on my sensors.)
I have used the devices for about 2 months now after making this change and have had no problems at all.
I seems to me that these problems are related to the BluetoothCacheMode Enum.
This indicates whether certain Bluetooth API methods should operate on values cached in the system or
retrieve those values from the Bluetooth device.
Using BluetoothCacheMode.Uncached attribute allows the service to update the attributes when needed.
If the device is paired then the BluetoothCacheMode is not needed(I think BluetoothCacheMode.Cached is default).
In your code the line:
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
Can be the cause of the connection lost if paired.
GetGattServicesAsync(), GetCharacteristicsAsync() and ReadValueAsync()
must have the attribute BluetoothCacheMode.Uncached when not paired, when paired default or BluetoothCacheMode.Cached.
See https://msdn.microsoft.com/en-us/library/windows/apps/dn263758.aspx.

c# multiple threads waiting for a ManualResetEvent

I'm messing around with multithreading and making some sort of task engine. The idea is that the engine can have a configurable amount of threads waiting and when a new task arrives the first free thread picks it up and executes it.
The problem is that something 2 threads pickup the same task somehow. I looked it through and I think that this code should work but obviously it doesn't. If I add the 10ms sleep where it is now commented out it works, but I'm not sure I understand why. It looks like the .Reset() function returns before it actually resets the event?
Can somebody explain? Is there a better way to let only a single thread continue when there are multiple waiting?
Thanks
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTest
{
public class Engine
{
private ManualResetEvent taskEvent;
private ConcurrentQueue<Task> tasks;
private bool running;
private List<Thread> threads;
private int threadAmount;
private int threadsBusy = 0;
public Engine(int amountOfThreads)
{
taskEvent = new ManualResetEvent(false);
tasks = new ConcurrentQueue<Task>();
threads = new List<Thread>();
threadAmount = amountOfThreads;
}
public void Start()
{
running = true;
for (var i = 0; i < threadAmount; i++)
{
var thread = new Thread(Process);
thread.Name = "Thread " + i;
threads.Add(thread);
thread.Start();
}
}
public void Stop()
{
running = false;
taskEvent.Set();
threads.ForEach(t => t.Join());
}
private void Process()
{
while (running)
{
lock (taskEvent)
{
// Lock it so only a single thread is waiting on the event at the same time
taskEvent.WaitOne();
taskEvent.Reset();
//Thread.Sleep(10);
}
if (!running)
{
taskEvent.Set();
return;
}
threadsBusy += 1;
if (threadsBusy > 1)
Console.WriteLine("Failed");
Task task;
if (tasks.TryDequeue(out task))
task.Execute();
threadsBusy -= 1;
}
}
public void Enqueue(Task t)
{
tasks.Enqueue(t);
taskEvent.Set();
}
}
}
EDIT
Rest of the code:
namespace TaskTest
{
public class Start
{
public static void Main(params string[] args)
{
var engine = new Engine(4);
engine.Start();
while (true)
{
Console.Read();
engine.Enqueue(new Task());
}
}
}
}
namespace TaskTest
{
public class Task
{
public void Execute()
{
Console.WriteLine(Thread.CurrentThread.Name);
}
}
}
When using Console.Read() on a key press, two characters are read from the input. You should use Console.ReadLine() instead.
Note that your code can be simplified a lot by using a BlockingCollection to handle the synchronization:
public class Engine
{
private BlockingCollection<Task> tasks;
private List<Thread> threads;
private int threadAmount;
public Engine(int amountOfThreads)
{
tasks = new BlockingCollection<Task>();
threads = new List<Thread>();
threadAmount = amountOfThreads;
}
public void Start()
{
for (var i = 0; i < threadAmount; i++)
{
var thread = new Thread(Process);
thread.Name = "Thread " + i;
threads.Add(thread);
thread.Start();
}
}
public void Stop()
{
tasks.CompleteAdding();
threads.ForEach(t => t.Join());
}
private void Process()
{
foreach (var task in tasks.GetConsumingEnumerable())
{
task.Execute();
}
}
public void Enqueue(Task t)
{
tasks.Add(t);
}
}

Updating the main form's progress bar from an external class?

I have a main form with a progress bar on it, and I would like to update the progress bar from an external class called "Logic"... however, Logic is already being referenced to on the main form. If I try to reference the main form in the Logic to update the progress bar, I just get stack overflows.
While searching around, I came across a lot of topics about a BackgroundWorker... but that's not what I'm trying to use. I have specific places in my Logic class where I want to update the progress bar on the main form by using progressbar.PerformStep(). I've tried creating a method on the main form to update the progress bar and calling that from the Logic class, but once again it's lacking a reference... and I can't just use MainForm frm1 = new MainForm() without causing errors everywhere else. I'm feeling pretty stumped here.
[edit]
Here is the code with the solution (thanks to you guys)----
Main Form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Natural_Language_Processor
{
public partial class frm_Main : Form
{
Logic logic = new Logic();
public frm_Main()
{
InitializeComponent();
}
private void frm_Main_Load(object sender, EventArgs e)
{
Timer.Start();
}
private void btn_Enter_Click(object sender, EventArgs e)
{
logic.Progress += new Logic.ProgressDelegate(DisplayProgess);
logic.RaiseProgress(0);
logic.str_Input = txt_Input.Text;
logic.Prep_Input();
txt_Input.Text = "";
logic.RaiseProgress(100);
System.Threading.Thread.Sleep(100);
logic.RaiseProgress(0);
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void eraseToolStripMenuItem_Click(object sender, EventArgs e)
{
logic.EraseMemory();
}
public void DisplayProgess(int percent)
{
if (this.InvokeRequired)
{
this.Invoke(new Logic.ProgressDelegate(DisplayProgess), new Object[] { percent });
}
else
{
this.progbar.Value = percent;
}
}
}
Logic:
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
namespace Natural_Language_Processor
{
class Logic
{
Data oData = new Data();
public List<string> Words = new List<string>();
private System.Threading.Thread T = null;
public delegate void ProgressDelegate(int percent);
public event ProgressDelegate Progress;
#region Variables
public string str_Input;
public string[] WordArray;
#endregion
public void RaiseProgress(int percent)
{
if (Progress != null)
{
Progress(percent);
}
}
public void Prep_Input()
{
//Check for Input
if (String.IsNullOrEmpty(str_Input))
{
}
else
{
//Set everything to lower-case
str_Input = str_Input.ToLower();
RaiseProgress(10);
//Remove all punctuation
if (str_Input.Contains(","))
{
while (str_Input.Contains(","))
{
int int_index = str_Input.IndexOf(",");
str_Input = str_Input.Remove(int_index, 1);
}
}
if (str_Input.EndsWith("."))
{
str_Input = str_Input.Trim('.');
}
else if (str_Input.EndsWith("?"))
{
str_Input = str_Input.Trim('?');
}
RaiseProgress(20);
//Split the sentence into an array of individual words
WordArray = str_Input.Split(' ');
RaiseProgress(30);
//Get current words (and max ID) from the database
int max_index = 0;
oData.GetWords();
Words.Clear();
if (oData.WordDataSet.Count > 0)
{
for (int i = 0; i < oData.WordDataSet.Count; i++)
{
max_index = oData.WordDataSet[i].ID;
Words.Add(oData.WordDataSet[i].Word);
}
}
RaiseProgress(40);
//Check each word in the sentence
for (int i = 0; i < WordArray.Length; i++)
{
//Update the frequency of an existing word in the database
if (Words.Contains(WordArray[i].ToString()))
{
oData.UpdateWords(WordArray[i].ToString());
}
else
{
//Or add the word
max_index = max_index + 1;
oData.InsertWordsTable(max_index, WordArray[i].ToString(), 1);
//And create its pre/pro word tables
oData.NewPreWordTable(WordArray[i].ToString());
oData.NewProWordTable(WordArray[i].ToString());
}
}
RaiseProgress(50);
//Check each word in the sentence after we have possibly created new pre/pro word tables in the previous code
for (int i = 1; i < WordArray.Length; i++)
{
oData.GetPreWords(WordArray[i].ToString());
Words.Clear();
//Get current pre_words from the database
for (int a = 0; a < oData.WordDataSet.Count; a++)
{
Words.Add(oData.WordDataSet[a].Word);
}
//Update the frequency of an existing word in the database
if (Words.Contains(WordArray[i - 1].ToString()))
{
oData.UpdatePreWords(WordArray[i].ToString(), WordArray[i - 1].ToString());
}
else
{
//Or add the word
oData.InsertPreWord(WordArray[i].ToString(), oData.GetPreWordIndex(WordArray[i].ToString()), WordArray[i - 1].ToString(), 1);
}
if (i == WordArray.Length - 1)
{
}
else
{
oData.GetProWords(WordArray[i].ToString());
Words.Clear();
//Get current pro_words from the database
for (int b = 0; b < oData.WordDataSet.Count; b++)
{
Words.Add(oData.WordDataSet[b].Word);
}
//Update the frequency of an existing word in the database
if (Words.Contains(WordArray[i + 1].ToString()))
{
oData.UpdateProWords(WordArray[i].ToString(), WordArray[i + 1].ToString());
}
else
{
//Or add the word
oData.InsertProWord(WordArray[i].ToString(), oData.GetProWordIndex(WordArray[i].ToString()), WordArray[i + 1].ToString(), 1);
}
}
}
RaiseProgress(60);
}
}
public void Respond()
{
RaiseProgress(70);
}
public void EraseMemory()
{
oData.GetWords();
Words.Clear();
for (int i = 0; i < oData.WordDataSet.Count; i++)
{
oData.DeletePreTable(oData.WordDataSet[i].Word);
oData.DeleteProTable(oData.WordDataSet[i].Word);
}
oData.DeleteWordsTable();
MessageBox.Show("Memory has been erased.");
}
}
}
Here's a loosely coupled approach, with the Logic() class raising a custom event instead of directly referencing the Form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Logic logic = new Logic();
logic.Progress += new Logic.ProgressDelegate(DisplayProgess);
logic.Start();
}
public void DisplayProgess(string message, int percent)
{
if (this.InvokeRequired)
{
this.Invoke(new Logic.ProgressDelegate(DisplayProgess), new Object[] { message, percent });
}
else
{
this.label1.Text = message;
this.progressBar1.Value = percent;
}
}
}
public class Logic
{
private System.Threading.Thread T = null;
public delegate void ProgressDelegate(string message, int percent);
public event ProgressDelegate Progress;
public void Start()
{
if (T == null)
{
T = new System.Threading.Thread(new System.Threading.ThreadStart(Worker));
T.Start();
}
}
private void Worker()
{
RaiseProgress("Initializing...", 0);
System.Threading.Thread.Sleep(1000); // simulated work
RaiseProgress("Loading Map...", 25);
System.Threading.Thread.Sleep(1500); // simulated work
RaiseProgress("Loading Sprites...", 50);
System.Threading.Thread.Sleep(1200); // simulated work
RaiseProgress("Loading Sound Effects...", 75);
System.Threading.Thread.Sleep(1700);
RaiseProgress("Loading Music...", 85);
System.Threading.Thread.Sleep(1100); // simulated work
RaiseProgress("Done!", 100);
}
private void RaiseProgress(string message, int percent)
{
if (Progress != null)
{
Progress(message, percent);
}
}
}

CancellationTokenSource misbehaviour

I have a problem when i wait for a task after i have canceled it with the CancellationTokenSource. The cancel call does not interrupt the task. When i wait
for the task the main thread blocks because the task will be never interrupted.
Here is a short description my program:
A task increments a char variable (from 'A' to 'Z') and shows it on the GUI thread. In order to do this the task executes a delegate (this.invoke()) on the thread the control was created on.
As soon as i comment out the RefreshTextBox()-Function the cancel call works and the task will be interrupted. It seems as if the this.invoke() command prevents the task from interrupting.
I the code below i have also implemented the same functionality with normal threads. And then i works. Where is the difference between task implementation and thread implementation?
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
Thread.Sleep(300);
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
this.Invoke(obj_StringHandler, new object[] { str_Value });
}
else
{
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
}
These 2 pieces of the code together form a deadlock:
_obj_Cts.Cancel();
_obj_Task.Wait();
and
this.Invoke(obj_StringHandler, new object[] { str_Value });
You are calling Wait() on the main thread, and Invoke() needs to be handled by the main thread.
You can break the deadlock by using this.BeginInvoke(...) instead.
The Thread version uses Interrupt, a sledgehammer. So the thread won't try to call RefreshTextBox() after the stop signal.
Here is the adapted code. I now call BeginInvoke() instead of Invoke() as Henk Holterman has proposed. This works very fine and is the only right way to prevent a deadlock. There is also another case that must be considered. I have also a IAsyncResult object that is given through the BeginInvoke() call. This object I use to check whether the async call has already completed or not. If I wouldn't check it it could be that the GUI thread is not fast enough (for example a sleep statement somewhere in the GUI thread) to execute my delegate and cause of this my TestTask() method would always call BeginInvoke() although the GUI thread has not already completed the last delegate. The result would be that my GUI thread would block the application.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace InvokeTest
{
public partial class frm_Main : Form
{
private delegate void dgt_StringHandler(string str_Value);
CancellationTokenSource _obj_Cts = null;
Thread _obj_Thread = null;
Task _obj_Task = null;
IAsyncResult _obj_Ar = null;
public frm_Main()
{
InitializeComponent();
}
private void CreateChar(ref char chr_Value)
{
int int_Value;
int_Value = (int)chr_Value;
int_Value++;
if (int_Value > 90 || int_Value < 65)
int_Value = 65;
chr_Value = (char)int_Value;
}
private void TestThread()
{
char chr_Value = '#';
bool bol_Stop = false;
while (!bol_Stop)
{
try
{
Thread.Sleep(1); // is needed for interrupting the thread
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
catch (ThreadInterruptedException)
{
bol_Stop = true;
}
}
}
private void TestTask(object obj_TokenTmp)
{
char chr_Value = '#';
CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
while (!obj_Token.IsCancellationRequested)
{
CreateChar(ref chr_Value);
RefreshTextBox(chr_Value.ToString());
}
}
private void RefreshTextBox(string str_Value)
{
if (txt_Value.InvokeRequired)
{
if (_obj_Ar == null ||
_obj_Ar.IsCompleted)
{
dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
_obj_Ar = this.BeginInvoke(obj_StringHandler, new object[] { str_Value });
}
}
else
{
Thread.Sleep(200);
txt_Value.Text = str_Value;
}
}
private void btn_StartStop_Click(object sender, EventArgs e)
{
if (_obj_Task == null && _obj_Thread == null)
{
if (opt_Task.Checked)
{
_obj_Cts = new CancellationTokenSource();
_obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
_obj_Task.Start();
}
else
{
_obj_Thread = new Thread(new ThreadStart(TestThread));
_obj_Thread.Start();
}
btn_StartStop.Text = "Stop";
}
else
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
_obj_Thread = null;
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
_obj_Task = null;
_obj_Cts = null;
}
btn_StartStop.Text = "Start";
}
}
private void frm_Main_FormClosing(object sender, FormClosingEventArgs e)
{
if (_obj_Thread != null)
{
_obj_Thread.Interrupt();
_obj_Thread.Join();
}
if (_obj_Task != null)
{
_obj_Cts.Cancel();
_obj_Task.Wait();
}
}
}
}

Categories