i am trying to use a third party telnet library "active expert" for a basic telnet session.
in my UI code behind i have something like
private async void Button_Click(object sender, RoutedEventArgs e)
{
var ts = new TelnetService();
await ts.DoConnect(node);
}
and my TelnetService looks like this
public class TelnetService
{
private Tcp objSocket = new Tcp();
private NwConstants objConstants = new NwConstants();
public string Responses { get; set; }
private Timer timer1 = new Timer();
public TelnetService()
{
timer1.Elapsed += timer1_Elapsed;
timer1.Interval = 100;
timer1.Start();
}
void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
public Task DoConnect(Node node)
{
return Task.Factory.StartNew(() =>
{
objSocket.Protocol = objConstants.nwSOCKET_PROTOCOL_TELNET;
objSocket.Connect(node.IP, 23);
while (true)
{
if ((Responses == null) || (!Responses.Contains(node.WaitString))) continue;
//do something
Responses = "";
break;
}
});
}
}
there are two important pieces of functionalities.
First in the timer1_Elapsed function which is process that will keeps on ruining and checks if there is data on socket, and if there is, it will append it to a string "Response". and i am using "timer" for it.
Second in the DoConnect function which will check the"Response" string for a certain input. for this i am using async await and Task.
in a nutshell first one accumulating the Response and Second one checking the Response.
Problem is that it looks like the timer code in general and
objSocket.ReceiveString()
line specifically is causing the UI thread to halt for several seconds. which means after clicking the button i cannot move my main form on the screen however the code is running in a separate thread.
i have tried using pure Thread for this but it didn't helped either.
update
instead of timer i am using a method AccumulateResponse
public static void AccumulateResponse()
{
while (true)
{
if (objSocket.ConnectionState == objConstants.nwSOCKET_CONNSTATE_CONNECTED)
{
if (objSocket.HasData())
{
Responses += objSocket.ReceiveString() + "\r\n";
}
}
}
}
and calling it like
var t = new Task(TelnetService.AccumulateResponse);
t.Start();
await TelnetService.DoConnect(node);
still no luck
The DoConnect isn't your problem. It is your Timer Elapsed Event handler.
The timer elapsed event is NOT asynchronous. Only the DoConnect is.
If there is no asynchronous version of ReceiveString() from your third party lib, then use Task.Run there as well inside of an async timer1_elapsed method.
Related
Setup:
Win10 .NET 4.7.1/VS2017 .NET 4.5/ C#
Level:
Beginner/Intermediate/new to threading
Objective:
1: A selenium web automation class that is triggered by a timer class so that the web automation class can exchange data with a javascript site at specific times.
2: Should be possible to migrate solution from WebForms to .NET library (dll).
Problem:
Step 1. Timer class sends time event to method in Web class to login to internet site = working.
Step 2. Web automation class (WinForms/GUI) tries to retrieve data from the method that is triggered by timer class event = Exception: "Calling thread cannot access this object because a different thread owns it." (as translated from swe).
I admit I´m confused by the terminology in the area of threading that is new to me. Also, I understand some multithreading techniques are only valid for WinForms. Since my objective is to migrate the solution to a dll these are not an option for me. I´ve played around with Invoke(), but as I understand it´s limited to use in WinForms. Guidance is highly appreciated!
WEB AUTOMATION CLASS:
private EdgeDriver driver;
private SeleniumHelper helper;
private WebAutomationTimer timer;
private double account;
public double Account { get => this.account; set => this.account = value; }
public Form1()
{
InitializeComponent();
timer = new WebAutomationTimer(02, 36, 00, 02, 38, 00);
timer.OnLoginTime += Timer_OnLoginTime;
timer.OnLogoutTime += Timer_OnLogoutTime;
}
private void Timer_OnLoginTime()
{
Login();
}
private void Timer_OnLogoutTime()
{
Logout();
}
public bool Login()
{
try
{
// working login code
UpdateLabels();
}
catch (Exception e)
{
}
}
private void UpdateLabels()
{
// EXCEPTION !!!
lblAccount.Text = GetAccount();
// EXCEPTION !!!
}
TIMER CLASS:
class WebAutomationTimer
{
public event TimerEvent OnLoginTime;
public event TimerEvent OnLogoutTime;
//public event TimerEvent OnSecond;
private System.Timers.Timer timer;
private DateTime now;
private int loginHour;
private int loginMin;
private int loginSec;
private int logoutHour;
private int logoutMin;
private int logoutSec;
public WebAutomationTimer(int loginHour, int loginMin, int loginSec, int logoutHour, int logoutMin, int logoutSec)
{
timer = new System.Timers.Timer();
timer.Interval = 1000; // 1 sec
timer.Elapsed += Timer_Elapsed;
timer.Start();
this.loginHour = loginHour;
this.loginMin = loginMin;
this.loginSec = loginSec;
this.logoutHour = logoutHour;
this.logoutMin = logoutMin;
this.logoutSec = logoutSec;
}
// Each second event
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
now = DateTime.Now;
//OnSecond();
//login
if (now.Hour == loginHour && now.Minute == loginMin && now.Second == loginSec)
OnLoginTime();
//logout
if (now.Hour == logoutHour && now.Minute == logoutMin && now.Second == logoutSec)
OnLogoutTime();
}
}
}
When you want to update View's control from another Thread it must show you error. Because it is using by UI Thread. In this case you have to use SynchronizationContext class or you can send Delegate to the App.Current.Dispatcher.BeginInvoke(delegate must be here);
SynchronizationContext _context = SynchronizationContext.Current;
private void UpdateLabels()
{
_context.Post(x=>
{
lblAccount.Text = AccountBalance.ToString();
},null),
//...
}
Alternative of SynchronizationContext :
private void UpdateLabels()
{
var action = new Action(() =>
{
lblAccount.Text = AccountBalance.ToString();
});
App.Current.Dispatcher.BeginInvoke(action);
//...
}
Both of them are same.
UI thread adapted for keyboard event and mouse event.
When you App.Current.Dispatcher.BeginInvoke(delegate) you say to the UI Thread that
"execute this too".
In addition you can suppose UI Thread like this
while(!thisApplication.Ended)
{
wait for something to appear in message queue
Got something : what kind of this message?
Keyboard/Mouse message --> fire event handler
User BeginInvoke message --> execute delegate
User Invoke message --> execute delegate & post result
}
this error is beacuse of change lable text beacuse lable is in another thread you can use this code
lblAccount.Invoke(new EventHandler((s, ee) => { lblAccount.Text = AccountBalance.ToString(); }));
This solution is probably only valid in my case. OP can delete this question if it´s believed to be a duplicate.
The first objective with was an easy to develop/run/debug situation with a GUI. Setting properties causes no cross thread exception. Showing the properties in a MessageBox.Show() causes no exception either. Hence no cross thread issues to dodge in the development/GUI stage.
The second objective was to migrate to a dll, hence no need to interfere with a GUI thread.
/Thanks anyway
Here's my situation:
I have a WPF application, where I have a method which takes a lot of time to be completed. I don't want to lose UI responsiveness, so I'd like to call that method in another thread.
I won't paste here my entire code, because it's too long, instead I wrote this short program, which represents well what I'm dealing with:
public void MainWindow()
{
InitializeComponent();
ProcessThread = new Thread(TimeConsumingMethod);
ProcessThread.Name = "ProcessThread";
ProcessThread.Start();
}
public void TimeConsumingMethod()
{
this.Dispatcher.Invoke(() =>
{
MytextBlock.Text = "new text";
MyOtherTextBlock.Text = "Hello";
});
for (int i = 0; i < 50; i++)
{
Debug.WriteLine("Debug line " + i);
}
if (MyRadioButton.IsChecked == false) //????????????????
{
while (true)
{
if (DateTime.Now >= timePicker.Value)
break;
}
}
OtherMethod();
}
Actually, I have two questions for the above code:
1. Everytime I want to access UI controls in my code I have to use this.Dispatcher.Invoke() =>.... Is it the right thing to do? I mean, I have a few places in my method (in my real code) where I check the state of some controls and everytime I need to do his Dispatcher.invoke thing - isn't there a better way to acces these controls?
2. In the code above, there's IF block in the end - in that block I'm checking the state of my RadioButton. Inside of that IF, I have a time consuming code. I cannot just do this:
this.Dispatcher.Invoke(() =>
{
if (MyRadioButton.IsChecked == false) //????????????????
{
while (true)
{
if (DateTime.Now >= timePicker.Value)
break;
}
}
});
That code would tell my UI thread to handle this if block - but I don't want that! That would cause the whole UI to freeze until this IF block gets done. How should I handle this situation?
Well, there are a lot of ways to implement what you are trying to do. One of them might look like this:
public MainWindow() {
InitializeComponent();
Initialize(); //do some intialization
}
private async void Timer_Tick(object sender, EventArgs e) {
if (DateTime.Now >= timePicker.SelectedDate) { //check your condition
timer.Stop(); //probably you need to run it just once
await Task.Run(() => OtherMethod()); //instead of creating thread manually use Thread from ThreadPool
//use async method to avoid blocking UI during long method is running
}
}
private readonly DispatcherTimer timer = new DispatcherTimer(); //create a dispatcher timer that will execute code on UI thread
public void Initialize() {
MytextBlock.Text = "new text";
MyOtherTextBlock.Text = "Hello"; //access UI elements normally
for (var i = 0; i < 50; i++) {
Debug.WriteLine("Debug line " + i);
}
if (MyRadioButton.IsChecked == false)
{
timer.Interval = TimeSpan.FromSeconds(10); // during init setup timer instead of while loop
timer.IsEnabled = true;
timer.Tick += Timer_Tick; //when 10 sec pass, this method is called
timer.Start();
}
}
public void OtherMethod() {
//long running method
Thread.Sleep(1000);
}
I've added some comments, but the main idea is this:
Don't create threads manually, use ThreadPool
Don't loop to wait for something, use timer to periodically check for it
Use async method when you have I/O Tasks
I have a WinForms application that consists of a main UI thread and 4 tasks. My main form has a private member level variable like this:
private bool keepThreadsRunning = false;
In the Load() event of my main form, I have the following:
keepThreadsRunning = true;
var task1Worker = Task.Factory.StartNew(() => DoStuff1());
var task2Worker = Task.Factory.StartNew(() => DoStuff2());
var task3Worker = Task.Factory.StartNew(() => DoStuff3());
var task4Worker = Task.Factory.StartNew(() => DoStuff4());
Inside of each of my DoStuff() methods, I basically have this:
while (keepThreadsRunning)
{
// do work here
Thread.Sleep(30000); // a couple of my tasks only need to run every 30 seconds or so
}
Lastly, in my Form_Closing() event handler, I have the following:
keepThreadsRunning = false;
this.Close();
Watching my application in task manager, it appears that the process is ending when I close my form but I'm a little confused about the four tasks. Is my call to this.Close() really causing those tasks to terminate (even if they're in the Thread.Sleep() call when it happens)? And is there a better way of accomplishing this than the way I'm coding it right now?
EDIT - I've looked briefly at task cancellation (when my app exits) but my understanding is that my tasks would need to periodically check the cancellation token to determine if they've been cancelled. Given that some of my tasks need to run every 30 seconds, I couldn't figure out how I'd implement that 30s wait (currently a Thread.Sleep()) and still have the task be checking the cancellation token.
Rather than using a boolean and Thread.Sleep(), use a WaitHandle, specifically a ManualResetEvent, created like this:
var threadTerminationHandle = new ManualResetEvent(false);
In your thread:
do {
// do work here
} while (!threadTerminationHandle.WaitOne(TimeSpan.FromSeconds(30))
This will wait until the WaitHandle is set, or 30 seconds elapses, whichever is sooner.
In your form:
threadTerminationHandle.Set();
Close();
First of all, closing the main UI thread will terminate your other tasks. If you need them to keep running, maybe consider running them in a seperate Console Application, or a Windows Service.
Even if you found a way to delay the closing of the form while you finish running the methods you need to run, this would only work if the end user closed the form in the way you wanted, and Windows being Windows there are a million and one ways to close an application so there is no guarantee that this will work.
For running a method asynchronously every x amount of seconds, you could just use a timer for the whole thing, like so:
using System;
using System.Timers;
using System.Windows.Forms;
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var timer1 = new System.Timers.Timer { Interval = 30000, Enabled = true };
var timer2 = new System.Timers.Timer { Interval = 20000, Enabled = true };
var timer3 = new System.Timers.Timer { Interval = 10000, Enabled = true };
var timer4 = new System.Timers.Timer { Interval = 5000, Enabled = true };
timer1.Elapsed += timer1_Elapsed;
timer2.Elapsed += timer2_Elapsed;
timer3.Elapsed += timer3_Elapsed;
timer4.Elapsed += timer4_Elapsed;
}
void timer4_Elapsed(object sender, ElapsedEventArgs e)
{
//do work here
}
void timer3_Elapsed(object sender, ElapsedEventArgs e)
{
//do work here
}
void timer2_Elapsed(object sender, ElapsedEventArgs e)
{
//do work here
}
void timer1_Elapsed(object sender, ElapsedEventArgs e)
{
//do work here
}
}
}
When you close application, tasks will be closed accordingly because task is processed under background thread from thread pool. So, you don't need to periodically check the cancellation token to determine if they've been cancelled
I have an UI, a custom class, and a thread. I want to run the custom class completely in a separate thread. Is there a clean way of doing this?
For example. On the MainForm below, when UI calls _threadOneClass.Sleep, I need the UI to go to the spawned ThreadOne and invoke the Sleep method in ThreadOne, not in the main thread.
Basically, all method calls in MyClass need to be executed in ThreadOne, not in main thread. It is like, the MyClass runs on its own "process", while still visible to be called from MainForm.
The MainForm has 3 buttons, and 1 textbox for logging.
I was thinking of deriving the Thread class, but it is sealed. So deriving is definitely a wrong way per Microsoft.
Help dear experts?
Here is the output (MainThread ID=10, ThreadOne ID=11)
MyClass instantiated
Starting ThreadOne
11-Run.start
Sleeping ThreadOne
10-Run.sleep for 3000 'Need this to run on ThreadID 11
10-Run.woke up 'Need this to run on ThreadID 11
Stopping ThreadOne
11-Run.done
Here is how the code look like.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private Thread _threadOneThread;
private MyClass _threadOneClass;
private void btnThreadOneCreate_Click(object sender, EventArgs e)
{
_threadOneClass = new MyClass(this);
_threadOneThread = new Thread(new ThreadStart(_threadOneClass.Run));
_threadOneThread.Start();
}
private void btnThreadOneStop_Click(object sender, EventArgs e)
{
_threadOneClass.IsRunning = false;
}
private void btnThreadOneSleep_Click(object sender, EventArgs e)
{
_threadOneClass.Sleep(3000);
}
public void Log(string txt)
{
MainForm.SetText(txtLog, txt);
}
internal static void SetText(Control ctl, string val)
{
if (ctl.InvokeRequired)
ctl.Invoke((MethodInvoker)delegate() { ctl.Text += Environment.NewLine + val; });
else
ctl.Text += Environment.NewLine + val;
}
}
class MyClass
{
public MyClass(MainForm frm)
{
_mainForm = frm;
}
private MainForm _mainForm;
public bool IsRunning = true;
public void Run()
{
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.start");
while (IsRunning) { }
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.done");
}
public void Sleep(int milliseconds)
{
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.sleep for " + milliseconds.ToString());
Thread.Sleep(milliseconds);
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.woke up");
}
}
Threads allow you to run heavy operations while you continue doing other things. In the case of user interfaces (your scenario), asynchronous behavior is almost always necessary as blocking the UI thread will cause to be unresponsive to the user and just isn't an option.
Luckily, the folks at Microsoft has made it extremely easy to write the same code, but in an asynchronous manner. I usually use Tasks because I like the control you get over the operation as well as the ContinueWith() lets you control what you do with the result should you need to propagate data back to the calling thread. If you prefer to use threads, ThreadPool.QueueUserWorkItem is just as easy.
Any operation you do not want to block the UI thread wrap it like this,
Task.Factory.StartNew(() => Object.PerformOperation());
or
ThreadPool.QueueUserWorkItem(new WaitCallback((x) => Object.PeroformOperation()));
I find this allows me to write the same exact code, but without blocking the UI thread. If you have several statements to execute you can use a block as well.
Task.Factory.StartNew(() =>
{
// do something
// do more stuff
// done
}).ContinueWith((completedTask) =>
{
// if you were computing a value with the task
// you can now do something with it
// this is like a callback method, but defined inline
// use ui's dispatcher if you need to interact with ui compontents
UI.Label.Dispatcher.Invoke(new Action(() =>
UI.Item.Label.Text = completedTask.Result;
}
The upcoming async features that are being released in the next .net version actually streamline this even more! But since it uses tasks you will still want to get comfortable with using them.
// this will begin the operation, then return control back to the ui so it does not hang.
var result = await Object.PerformLongTask();
// once the long task is completed then it continues and you can use the result
UI.Item.Label = result;
To give a real example, here is some code from an FTP client I wrote which has has a WPF front end. When the start button is clicked the ftp transfer is launched in it's own task, then a while loop which updates the interface every half a second is launched in a task, so neither interferes with the interface thread. Again it's the same code, just wrapped in lambada's.
private void btnStart_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
ftp.Mirror(#"C:\LocalFolder", "/RemoteFolder", 10));
Task.Factory.StartNew(() =>
{
while (true)
{
lbPercentSuccess.Dispatcher.Invoke(new Action(() =>
{
lbPercentSuccess.Content = ftp.FtpProgress.SuccessPercentage;
lbPercentError.Content = ftp.FtpProgress.ErrorPercentage;
lbPercentTotal.Content = ftp.FtpProgress.TotalPercentage;
lbDuration.Content = ftp.FtpProgress.Duration;
}));
Thread.Sleep(500);
}
});
}
This is not possible to my knowledge. You can only run and invoke individual methods or queue them on separate threads when need be. Setting an actual object on a separate thread defeats your purpose. This is because you only going to harness the benefits of multithreading when invoking a method on a separate thread not an object.
then reassign the del to MethodTwo... and so on. This is made easier if you conform to a method signature.
Possible solution:
Thread threadTest = new Thread(new ThreadStart(MethodOne));
threadTest = new Thread(new ThreadStart(MethodTwo));
threadTest.Start();
Or
Action del = TestClass.MethodOne;
IAsyncResult result = del.BeginInvoke(null, null);
Func<int,int> del = TestClass.MethodOne;
IAsyncResult result = del.BeginInvoke(11,null, null);
int value = del.EndInvoke(result);
It's not simple, but have a look at this. It's a nice explination of how to use cross thread communication.
http://www.codeproject.com/KB/cs/delegatequeue.aspx
So far, this is what I found (from iPhone development). The Run loop acts like a spine that invokes various methods. It is implemented like the following:
A more elegant solution is welcomed.
class MyClass
{
public MyClass(MainForm frm)
{
_mainForm = frm;
}
private MainForm _mainForm;
public bool IsRunning = true;
public void Run()
{
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.start");
while (IsRunning)
{
if (_runSleepMilliSeconds != null)
{
_Sleep(_runSleepMilliSeconds ?? 3000);
_runSleepMilliSeconds = null;
}
}
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.done");
}
private int? _runSleepMilliSeconds = null;
public void Sleep(int milliseconds)
{
_runSleepMilliSeconds = milliseconds;
}
private void _Sleep(int milliseconds)
{
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.sleep for " + milliseconds.ToString());
Thread.Sleep(milliseconds);
_mainForm.Log(Thread.CurrentThread.ManagedThreadId.ToString() + "-Run.woke up");
}
}
I have a form with 2 comboboxes on it. And I want to fill combobox2.DataSource based on combobox1.Text and combobox2.Text (I assume that the user has completed input in combobox1 and is in the middle of inputting in combobox2). So I have an event handler for combobox2 like this:
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
As far as building DataSource is time-consuming process (it creates a request to database and executes it) I decided that it's better to perform it in another process using BackgroundWorker. So there's a scenario when cmbDataSourceExtractor hasn't completed its work and the user types one more symbol. In this case I get an exception on this line
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues ); about that BackgroundWorker is busy and cannot perform several actions in the same time.
How to get rid of this exception?
CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being run in the background must periodically check this property and handle the cancellation itself.
The tricky part is that your DoWork delegate is probably blocking, meaning that the work you do on your DataSource must complete before you can do anything else (like check for CancellationPending). You may need to move your actual work to yet another async delegate (or maybe better yet, submit the work to the ThreadPool), and have your main worker thread poll until this inner worker thread triggers a wait state, OR it detects CancellationPending.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
If you add a loop between the CancelAsync() and the RunWorkerAsync() like so it will solve your problem
private void combobox2_TextChanged(object sender, EventArgs e)
{
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
The while loop with the call to Application.DoEvents() will hault the execution of your new worker thread until the current one has properly cancelled, keep in mind you still need to handle the cancellation of your worker thread. With something like:
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
The Application.DoEvents() in the first code snippet will continue to process your GUI threads message queue so the even to cancel and update the cmbDataSourceExtractor.IsBusy property will still be processed (if you simply added a continue instead of Application.DoEvents() the loop would lock the GUI thread into a busy state and would not process the event to update the cmbDataSourceExtractor.IsBusy)
You will have to use a flag shared between the main thread and the BackgroundWorker, such as BackgroundWorker.CancellationPending. When you want the BackgroundWorker to exit, just set the flag using BackgroundWorker.CancelAsync().
MSDN has a sample: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancellationpending.aspx
MY example . DoWork is below:
DoLengthyWork();
//this is never executed
if(bgWorker.CancellationPending)
{
MessageBox.Show("Up to here? ...");
e.Cancel = true;
}
inside DoLenghtyWork :
public void DoLenghtyWork()
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
inside OtherStuff() :
public void OtherStuff()
{
for(int i=0 ; i<10000000; i++)
{ int j = i/3; }
}
What you want to do is modify both DoLenghtyWork and OtherStuff() so that they become:
public void DoLenghtyWork()
{
if(!bgWorker.CancellationPending)
{
OtherStuff();
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
public void OtherStuff()
{
if(!bgWorker.CancellationPending)
{
for(int i=0 ; i<10000000; i++)
{
int j = i/3;
}
}
}
The problem is caused by the fact that cmbDataSourceExtractor.CancelAsync() is an asynchronous method, the Cancel operation has not yet completed when cmdDataSourceExtractor.RunWorkerAsync(...) exitst. You should wait for cmdDataSourceExtractor to complete before calling RunWorkerAsync again. How to do this is explained in this SO question.
My answer is a bit different because I've tried these methods but they didn't work. My code uses an extra class that checks for a Boolean flag in a public static class as the database values are read or where I prefer it just before an object is added to a List object or something as such. See the change in the code below. I added the ThreadWatcher.StopThread property. for this explation I'm nog going to reinstate the current thread because it's not your issue but that's as easy as setting the property to false before accessing the next thread...
private void combobox2_TextChanged(object sender, EventArgs e)
{
//Stop the thread here with this
ThreadWatcher.StopThread = true;//the rest of this thread will run normally after the database function has stopped.
if (cmbDataSourceExtractor.IsBusy)
cmbDataSourceExtractor.CancelAsync();
while(cmbDataSourceExtractor.IsBusy)
Application.DoEvents();
var filledComboboxValues = new FilledComboboxValues{ V1 = combobox1.Text,
V2 = combobox2.Text};
cmbDataSourceExtractor.RunWorkerAsync(filledComboboxValues );
}
all fine
private void cmbDataSourceExtractor_DoWork(object sender, DoWorkEventArgs e)
{
if (this.cmbDataSourceExtractor.CancellationPending)
{
e.Cancel = true;
return;
}
// do stuff...
}
Now add the following class
public static class ThreadWatcher
{
public static bool StopThread { get; set; }
}
and in your class where you read the database
List<SomeObject>list = new List<SomeObject>();
...
if (!reader.IsDbNull(0))
something = reader.getString(0);
someobject = new someobject(something);
if (ThreadWatcher.StopThread == true)
break;
list.Add(something);
...
don't forget to use a finally block to properly close your database connection etc. Hope this helps! Please mark me up if you find it helpful.
In my case, I had to pool database for payment confirmation to come in and then update WPF UI.
Mechanism that spins up all the processes:
public void Execute(object parameter)
{
try
{
var url = string.Format("{0}New?transactionReference={1}", Settings.Default.PaymentUrlWebsite, "transactionRef");
Process.Start(new ProcessStartInfo(url));
ViewModel.UpdateUiWhenDoneWithPayment = new BackgroundWorker {WorkerSupportsCancellation = true};
ViewModel.UpdateUiWhenDoneWithPayment.DoWork += ViewModel.updateUiWhenDoneWithPayment_DoWork;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerCompleted += ViewModel.updateUiWhenDoneWithPayment_RunWorkerCompleted;
ViewModel.UpdateUiWhenDoneWithPayment.RunWorkerAsync();
}
catch (Exception e)
{
ViewModel.Log.Error("Failed to navigate to payments", e);
MessageBox.Show("Failed to navigate to payments");
}
}
Mechanism that does checking for completion:
private void updateUiWhenDoneWithPayment_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(30000);
while (string.IsNullOrEmpty(GetAuthToken()) && !((BackgroundWorker)sender).CancellationPending)
{
Thread.Sleep(5000);
}
//Plug in pooling mechanism
this.AuthCode = GetAuthToken();
}
Mechanism that cancels if window gets closed:
private void PaymentView_OnUnloaded(object sender, RoutedEventArgs e)
{
var context = DataContext as PaymentViewModel;
if (context.UpdateUiWhenDoneWithPayment != null && context.UpdateUiWhenDoneWithPayment.WorkerSupportsCancellation && context.UpdateUiWhenDoneWithPayment.IsBusy)
context.UpdateUiWhenDoneWithPayment.CancelAsync();
}
I agree with guys. But sometimes you have to add more things.
IE
1) Add this worker.WorkerSupportsCancellation = true;
2) Add to you class some method to do the following things
public void KillMe()
{
worker.CancelAsync();
worker.Dispose();
worker = null;
GC.Collect();
}
So before close your application your have to call this method.
3) Probably you can Dispose, null all variables and timers which are inside of the BackgroundWorker.