I'm making an application that is reading the serial port and updating data in a WindowsForms application screen.
Sometimes when you try to close the program is locked.
I'm using delegate.
What should I be doing wrong?
void sp1_LineReceived(object sender, LineReceivedEventArgs Args)
{
Invoke(new Action(() =>
{
// execute code
}));
}
Init
public FormPrincipal()
{
InitializeComponent();
spSistema.LineReceived += new LineReceivedEventHandler(sp1_LineReceived);
// next codes
}
Outher codes
public partial class FormPrincipal : Form
{
SerialPort spSimulador = new SerialPort();
public static PortaSerial spSistema = new PortaSerial();
In general, BeginInvoke is preferable to Invoke, as it won't block. See here. You may well have a deadlock here.
void sp1_LineReceived(object sender, LineReceivedEventArgs Args)
{
BeginInvoke(new Action(() =>
{
// execute code
}));
}
Related
I have 2 questions about backgroundWorker: one is cancellation and another is invoking.
My code briefly looks like this:
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
}
for (int i = 0; i < 10; i++) {
System.Threading.Thread.Sleep(50);
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
EditProcess.Text(String.Format("Downloaded: {0}\r\n"));
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
First, while DnldBgWorker is running, I clicked StopBtn to stop the DnldBgWorker and the asynchronous work would not stop. How should I stop DnldBgWorker?
Second, EditProcess.Text(String.Format("Downloaded: {0}\r\n")); would give me an error that cross-thread operation is not valid. I know that I should make a delegate to do this, but I don't know exactly how.
++) My code looks like it's doing very simple works in very complicated way, but I put really essential elements in this code so please understand
Let's address the issue before we get into the code
For some reason, you have a completely redundant loop waiting for cancel after your actual download is done. Hence BtnStop does not work for you
When you call EditProcess.Text from Dnld which is invoked in the BackgroundWorker context, you are accessing a GUI element from a thread which does not "own" it. You can read in detail about cross-thread operation here. In your case, you should do it via your ReportProgress call.
Now you can see how I have
Removed the redundant loop from GoDownload while moving the if (DnldBgWorker.CancellationPending) check to the download loop. This should make the StopBtn work now.
Added the ProgressChanged event handler to do the GUI change in the ExecuteBtn_Click. This is triggered by DnldBgWorker.ReportProgress call in the download loop of GoDownload method. Here we pass the custom formatted string as UserState
Also make sure that you have the enabled the ReportsProgress and SupportsCancellation properties like below, perhaps in your designer property box or in code lile DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;
Hope everything else is clear with the code below.
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
DnldBgWorker.ProgressChanged += (s, e) => EditProcess.Text((string)e.UserState);;
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
DnldBgWorker.ReportProgress(0, String.Format($"Downloaded: {url}\r\n"));
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
There are 2 issues here:
Regarding cancellation - you need to check for cancellation status in the loop that does downloading (thus downloading only part of requested files), not in the later loop which I don't really understand.
As an additional side note you can avoid using BackgroundWorker by using WebClient.DownloadFileAsync and WebClient.CancelAsync combo.
As of reporting progress - make you BackgroundWorker report progress back to the UI thread via ReportProgress and update UI from there.
As for how to cancel a thread. Here is a basic example, for a console application, that I hope you can fit into your more complex code.
void Main()
{
var tokenSource = new CancellationTokenSource();
System.Threading.Tasks.Task.Run(() => BackgroundThread(tokenSource.Token));
Thread.Sleep(5000);
tokenSource.Cancel();
}
private void BackgroundThread(CancellationToken token)
{
while (token.IsCancellationRequested == false) {
Console.Write(".");
Thread.Sleep(1000);
}
Console.WriteLine("\nCancellation Requested Thread Exiting...");
}
The results would be the following.
.....
Cancellation Requested Thread Exiting...
Secondly, as far as how to invoke from your thread to interact with the user interface, hopefully this blog will help you. Updating Windows Form UI elements from another thread
Please let me know if you found this helpful.
To support cancellation you need to set the property
DnldBgWorker.WorkerSupportsCancellation = true;
It is not clear if you set it somewhere else, but you need it to cancel the background worker as you can read on MSDN
Set the WorkerSupportsCancellation property to true if you want the
BackgroundWorker to support cancellation. When this property is true,
you can call the CancelAsync method to interrupt a background
operation.
Also I would change the GoDownload method to
private void GoDownload(Download Dnld, string[] urllist, EventArgs e)
{
foreach(string url in urllist)
{
Dnld.Dnld(url);
// this is just to give more time to test the cancellation
System.Threading.Thread.Sleep(500);
// Check the cancellation after each download
if (DnldBgWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
}
For the second problem you need to call that method when your code is running on the UI thread and not in the background thread. You could easily achieve this moving the textbox update in the event handler for the ProgressChanged event. To set up the event handler you need another property set to true
DnldBgWorker.WorkerReportsProgress = true;
And set the event handler for the ProgressChanged event
DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;
private void DnldBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
EditProcess.Text(String.Format("Downloaded: {0}\r\n", e.ProgressPercentage));
}
and raise this event in the GoDownload with
DnldBgWorker.ReportProgress(i);
Question :
I'm a beginner in c# and i'm having a bit of trouble to understand how thread works with a form. I'm trying to update a progressbar when my program hit keypoints and i can't get it to work here is my code.
For my "worker" class :
public void addFollower(string followerName, Action<string> followerAction) {
this.followers.Add(followerName, followerAction);
}
private void notifyFollowers(string message) {
if (followers.Count > 0) {
foreach (String followerName in followers.Keys) {
followers[followerName].Invoke(message);
}
}
}
for my linking class (controller maybe?) :
public void runWithParams(Dictionary<string,string> parameters, Action<string> updateManager = null){
string app = parameters["appName"];
string navigator = parameters["navigatorName"];
string mode = parameters["mode"];
string scenario = parameters["scenarioName"];
try {
Scenario sc = this.scenarioBuilders[app].buildScenario(scenario);
if(updateManager != null) sc.addFollower("updateManager", updateManager);
Thread TestRunner = new Thread(sc.run);
TestRunner.Start();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine("Unexpected shutdown or driver unreachable");
}
}
For the gui :
private void ButtonRun_Click(object sender, EventArgs e) {
Dictionary<string, string> parameters = new Dictionary<string, string>{
{"appName",this.CBApplicationChoice.SelectedItem.ToString()},
{"navigatorName",this.CBNavigatorChoice.SelectedItem.ToString()},
{"mode",this.CBModeChoice.SelectedItem.ToString()},
{"scenarioName",this.CBScenarioChoice.SelectedItem.ToString()}
};
this.dispatcher.runWithParams(parameters, ManageRealTimeStep1);
}
public void ManageRealTimeStep1(string liveevent){
if (liveevent.Contains("NumberOfPages")) {
this.PBStep1.Maximum = Convert.ToInt32(liveevent.Split(':')[1]);
} else if (liveevent.Contains("StartingTestNewPage")) {
this.PBStep1.Increment(1);
}
}
I'm getting an InvalidOperationException when i click on the RunButton and the error says that i'm trying to call a function that is in another thread. How can i fix it?
Thanks in advance for any answer/ insights
Solution :
I changed the method in gui for :
public void ManageRealTimeStep1(string liveevent) {
BeginInvoke(new Action(() => {
if (liveevent.Contains("NumberOfPages")) {
this.PBStep1.Maximum = Convert.ToInt32(liveevent.Split(':')[1]);
} else if (liveevent.Contains("StartingTestNewPage")) {
this.PBStep1.Increment(1);
}
}));
}
Use BeginInvoke method:
BeginInvoke(new Action(() =>
{
this.PBStep1.Maximum = Convert.ToInt32(liveevent.Split(':')[1]);
}));
Read more about updating WinForms UI from another thread here
You are not allowed to update the GUI from a different thread, see How to update the GUI from another thread in C#?.
You are accessing the GUI from the ManageRealTimeStep1 method which is used by the Scenario class (as a callback) on the background thread.
my form freezes immediately when I run my code. I'm not sure why, but please take a look at the code below and see the screenshot. Basically when I run my code, the form freezes once my code loads, and it just says "not responding" what could it be doing?
namespace MySample
{
public class Driver
{
static void Main(string[] args)
{
log4net.Config.XmlConfigurator.Configure();
Form1 form = new Form1();
form.Show();
try
{
StartModbusSerialRtuSlave();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public static void StartModbusSerialRtuSlave()
{
using (SerialPort slavePort = new SerialPort("COM1"))
{
// configure serial port
slavePort.BaudRate = 38400;
slavePort.DataBits = 8;
slavePort.Parity = Parity.Odd;
slavePort.StopBits = StopBits.One;
slavePort.Open();
byte unitId = 1;
// create modbus slave
ModbusSlave slave = ModbusSerialSlave.CreateRtu(unitId, slavePort);
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();
slave.Listen();
}
}
}
CODE ON FORM
namespace MySample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
The function StartModbusSerialRtuSlave is not returning. The call to Listen likely blocks, which is normally fine, but its on the UI thread.
Because it isn't executing on its own thread (separate from the UI), it causes the application to "lock up" and deliver the error message you see.
Simple fix, don't perform long-running operations on the UI thread. Start things like I/O on their own thread.
For example:
log4net.Config.XmlConfigurator.Configure();
try
{
new Thread((p) => StartModbusSerialRtuSlave()).Start();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
//Start your form the right way!
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
I've made a little test program to try and get a USB card reader working using an ActiveX control provided by the manufacturer.
The form works fine as long as it doesn't use a separate thread. I create a new instance of Reader and listen to the Read event and everything works fine. I swipe a card, the event fires and the textbox gets updated.
private Reader _reader;
public Form1()
{
InitializeComponent();
CreateScanner();
}
public void CreateScanner()
{
_reader = new Reader();
_reader.Read += Read;
}
void Read(object sender, EventArgs e)
{
CardData.Text = "Card Read";
}
Reader class in case it helps:
public class Reader
{
private AxUSBHIDInsert _axUsbHid;
public event EventHandler Read;
public Reader()
{
_axUsbHid = new AxUSBHIDInsert();
_axUsbHid.CreateControl();
_axUsbHid.BeginInit();
_axUsbHid.MSRReadDir = MSRReadDirection.ReadWithdrawal;
_axUsbHid.EndInit();
_axUsbHid.PortOpen = true;
_axUsbHid.CardDataChanged += CardDataChanged;
}
void CardDataChanged(object sender, EventArgs e)
{
if (Read != null)
Read(this, new EventArgs());
}
}
However, I need to run this on a separate thread. So I change the constructor to be
Thread thread = new Thread(CreateScanner);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
It has to be an STA thread otherwise the ActiveX control will complain that it cannot be instantiated. However doing this, the event doesn't fire anymore. I'm not that familiar with how threading works, so I'm not sure why.
Any ideas? Note that it has to work this way (separate thread, hooked up to the Read event), because the code will eventually reside in a class library that gets deployed with an application that I cannot change.
Your COM object sends messages to the second thread, that means it must be alive all the time application is running.
Try to do like this:
public class Reader
{
public Reader()
{
// leave empty
}
public Read() {
_axUsbHid = new AxUSBHIDInsert();
...
}
}
public Form1()
{
InitializeComponent();
_reader = new Reader();
_reader.Read += Read;
StartRead(_reader);
}
void StartRead(Reader reader) {
Thread thread = new Thread(ReadRoutine);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(reader);
}
void ReadRoutine(object param) {
Reader reader = (Reader)param;
reader.Read();
while (AppIsAlive) { // add logic here
// bad code, import GetMessage from user32.dll
Application.DoEvents();
Thread.Sleep(100);
}
}
But the Read event must be processed synchronously:
void Read(object sender, EventArgs e)
{
if (this.InvokeRequired)
this.BeginInvoke(new EventHandler(Read), new object[2] { sender, e } );
else {
CardData.Text = "Card Read";
}
}
I have a form that starts a thread. Now I want the form to auto-close when this thread terminates.
The only solution I found so far is adding a timer to the form and check if thread is alive on every tick. But I want to know if there is a better way to do that?
Currently my code looks more less like this
partial class SyncForm : Form {
Thread tr;
public SyncForm()
{
InitializeComponent();
}
void SyncForm_Load(object sender, EventArgs e)
{
thread = new Thread(new ThreadStart(Synchronize));
thread.IsBackground = true;
thread.Start();
threadTimer.Start();
}
void threadTimer_Tick(object sender, EventArgs e)
{
if (!thread.IsAlive)
{
Close();
}
}
void Synchronize()
{
// code here
}
}
The BackgroundWorker class exists for this sort of thread management to save you having to roll your own; it offers a RunWorkerCompleted event which you can just listen for.
Edit to make it call a helper method so it's cleaner.
thread = new Thread(() => { Synchronize(); OnWorkComplete(); });
...
private void OnWorkComplete()
{
Close();
}
If you have a look at a BackgroundWorker, there is a RunWorkerCompleted event that is called when the worker completes.
For more info on BackgroundWorkers Click Here
Or
You could add a call to a complete function from the Thread once it has finished, and invoke it.
void Synchronize()
{
//DoWork();
//FinishedWork();
}
void FinishedWork()
{
if (InvokeRequired == true)
{
//Invoke
}
else
{
//Close
}
}
Have a look at delegates, IAsyncResult, BeginInvoke and AsyncCallback
At the end of your thread method, you can call Close() using the Invoke() method (because most WinForms methods should be called from the UI thread):
public void Synchronize()
{
Invoke(new MethodInvoker(Close));
}
Solution for arbitrary thread (e.g. started by some other code), using UnmanagedThreadUtils package:
// Use static field to make sure that delegate is alive.
private static readonly UnmanagedThread.ThreadExitCallback ThreadExitCallbackDelegate = OnThreadExit;
public static void Main()
{
var threadExitCallbackDelegatePtr = Marshal.GetFunctionPointerForDelegate(ThreadExitCallbackDelegate);
var callbackId = UnmanagedThread.SetThreadExitCallback(threadExitCallbackDelegatePtr);
for (var i = 1; i <= ThreadCount; i++)
{
var threadLocalVal = i;
var thread = new Thread(_ =>
{
Console.WriteLine($"Managed thread #{threadLocalVal} started.");
UnmanagedThread.EnableCurrentThreadExitEvent(callbackId, new IntPtr(threadLocalVal));
});
thread.Start();
}
UnmanagedThread.RemoveThreadExitCallback(callbackId);
}
private static void OnThreadExit(IntPtr data)
{
Console.WriteLine($"Unmanaged thread #{data.ToInt64()} is exiting.");
}