I have a button that starts two threads
private void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
}
and there are another buttons to pause, Resume, Stop those threads
My question is how can I disable (pause, Resume, Stop) buttons while the threads are working and re enable Crawl after the threads finished
Here is how you could start a Thread and have a way to await its completion:
public static Thread CreateAwaitableThread(Action action, out Task threadCompletion)
{
var tcs = new TaskCompletionSource<bool>();
threadCompletion = tcs.Task;
return new Thread(() =>
{
try
{
action();
}
finally
{
tcs.SetResult(true);
}
});
}
This method returns the newly created Thread, and also a Task that will be completed when the Thread is completed. You could use it like this:
private async void CrawdBtn_Click(object sender, EventArgs e)
{
CrawdBtn.Enabled = false;
Thread t1 = CreateAwaitableThread(AddLinksToList, out var t1c);
Thread t2 = CreateAwaitableThread(EnqueueFromList, out var t2c);
t1.Start();
t2.Start();
await Task.WhenAll(t1c, t2c);
CrawdBtn.Enabled = true;
}
In case of an exception the error will not be propagated through the Task. It is assumed that the delegates already include error handling logic. If not, an unhandled exception will occur as usual.
To solve your problem you can make a thread to check the ThreadState of thread t and thread b
private void btnstart_Click(object sender, EventArgs e)
{
t = new Thread(AddLinksToList);
b = new Thread(EnqueueFromList);
t.Start();
b.Start();
if (threadchecker == null)//this if determines Whether it's the first time or not
{
threadchecker = new Thread(() => ChekingStateOfThreads());
threadchecker.IsBackground = true;
threadchecker.Start();
}
}
Since the thread wants to check the ThreadState of those threads it should always run.
and This is ChekingStateOfThreads
private void ChekingStateOfThreads()
{
while (true)
{
Thread.Sleep(1000);
if (t.ThreadState == ThreadState.Stopped)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Running)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = btnresume.Enabled = false;
btnpause.Enabled = btnstop.Enabled = true;
}));
}
else if (t.ThreadState == ThreadState.Aborted)
{
this.Invoke(new Action(() =>
{
btnstart.Enabled = true;
btnpause.Enabled = btnresume.Enabled = btnstop.Enabled = false;
}));
}
else if (t.ThreadState == ThreadState.Suspended)
{
this.Invoke(new Action(() =>
{
btnpause.Enabled = btnstart.Enabled = false;
btnresume.Enabled = btnstop.Enabled = true;
}));
}
}
}
The concept of function is pretty simple. Each 1 second the thread is check the state of thread t.
See Why use Invoke on Controls in .net? to figure out why should we use INVOKE.
To abort the threadchecker just use the Form_closing Event
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
threadchecker.Abort();
}
Related
I am using an MVVM model in my WPF application. I have an command binding to the cancel button. I have a start button which starts a few background workers. When i click on the cancel button, i want all the background workers to stop/quit.
With my current code when i click on cancel button, the background worker does not stop and the "StartEngineeringOperation" finishes. Can anyone please help me out with what i am doing wrong here?
Current code:
For EngineeringViewModel.cs:
public class EngineeringViewModel{
public EngineeringViewModel()
{
StartEngineering= new DelegateCommand(o =>
{
worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
if (worker.IsBusy != true) worker.RunWorkerAsync();
worker.DoWork += (s, e) =>
{
StartEngineeringOperation();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
};
},
k => true);
Cancel = new DelegateCommand(CancelEngineeringOperation);
}
private void StartEngineeringOperation()
{
startAlarmService();
startTrendQualityCheck();
}
private void CancelEngineeringOperation(object param)
{
worker.DoWork += (s, e) =>
{
if (worker.IsBusy)
{
worker.CancelAsync();
e.Cancel = true;
return;
}
};
}
}
I tried this :
but doesn't seem to work:
private void StartEngineeringOperation()
{
startAlarmService();
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
startTrendQualityCheck();
}
As you may have learned from te comments, you need to poll the state of the BackgroundWorker in your operations that you want to support cancellation. Then take measures to cancel the ongoing operation gracefully.
The example shows how to cancel a background thread on button click. The first example uses the old BackgroundWorker and the second the modern and cleaner Task library.
BackgroundWorker
private BackgroundWorker Worker { get; set; }
private void StartWorker()
{
this.Worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
this.Worker.DoWork += BackgroundWorker_DoWork;
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DoCancellableWork();
// Stop BackgroundWorker from executing
if (worker.CancellationPending)
{
e.Cancel = true;
}
}
private void DoCancellableWork()
{
// Check for cancellation before executing the cancellable operation and allocating resources etc..
if (this.Worker.CancellationPending)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (this.Worker.CancellationPending)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundWorker_Click(object sender, EventArgs e)
{
if (this.Worker.WorkerSupportsCancellation)
{
this.Worker.CancelAsync();
}
}
Task library
The example uses Progress<T> to report progress from the background thread to the UI thread.
private CancellationTokenSource CancellationTokenSource { get; set; }
private async Task StartWorker()
{
this.CancellationTokenSource = new CancellationTokenSource();
// Prepare callback to update UI from the background thread.
// The Progress<T> instance MUST be created on the UI thread
IProgress<int> progressReporter = new Progress<int>(progress => this.ProgressBar.Value = progress);
await Task.Run(
() => DoWork(progressReporter, this.CancellationTokenSource.Token),
this.CancellationTokenSource.Token);
this.CancellationTokenSource.Dispose();
}
private void DoWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
DoCancellableWork(progressReporter, cancellationToken);
}
private void DoCancellableWork(IProgress<int> progressReporter, CancellationToken cancellationToken)
{
// Check for cancellation before executing the operation and allocating resources etc..
if (cancellationToken.IsCancellationRequested)
{
return;
}
// Periodically/regularly check for the cancellation flag
for (int i = 0; i <= 10000000000; i++)
{
if (cancellationToken.IsCancellationRequested)
{
// Cancel operation gracefully e.g., do some cleanup, free resources etc.
return;
}
// Do some work
// Report progress
progressReporter.Report(20);
}
}
// Alternatively use a command e.g., in a view model class
private void CancelBackgroundThread_Click(object sender, EventArgs e)
{
this.CancellationtokenSource?.Cancel();
}
Since the OP describes the task being done as "checking services", I would assume the work done looks something like this:
while(true){
// check service
// Post result back to UI thread
Thread.Sleep(...);
}
This is not the best way to write such such a check. As in most cases where Thread.Sleep is used, a timer would be a better alternative:
var myTimer = new System.Timers.Timer(...);
myTimer .Elapsed += OnTimedEvent;
myTimer .AutoReset = true;
myTimer .Enabled = true;
...
private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
// check service
// Post result back to UI thread
}
This makes the problem of stopping/starting the task being done a simple matter of changing the Enabled-flag of the timer. It is also possible to use a timer, or a synchronization context to run the event directly on the UI thread, this is probably the best solution if "checking services" only takes a few ms.
Im having this excepton in the invoke mothod and i don't know why.
System.InvalidOperationException: 'Operación no válida a través de subprocesos: Se tuvo acceso al control 'Form2' desde un subproceso distinto a aquel en que lo creó.'
Google translate this as:
System.InvalidOperationException: 'Invalid operation through threads: The' Form2 'control was accessed from a different thread than the one in which it was created.'
If I call the invoke from a button for example it works correctly but I need to call this from the FileSystemWatcher.
List<Thread> listThreads = new List<Thread>();
private void Form1_Load(object sender, EventArgs e)
{
RunFileSystemWatcher();
}
public void RunFileSystemWatcher()
{
FileSystemWatcher fsw = new FileSystemWatcher();
fsw.Path = "C:/Users/Gaming/Documents";
fsw.NotifyFilter = NotifyFilters.LastAccess;
fsw.NotifyFilter = NotifyFilters.LastWrite;
//fsw.NotifyFilter = NotifyFilters.Size;
//fsw.Created += FileSystemWatcher_Created;
fsw.Changed += FileSystemWatcher_Changed;
fsw.Filter = "*.txt";
fsw.EnableRaisingEvents = true;
}
Boolean abrir = true;
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (abrir) {
for (int i=0; i<5; i++)
{
Thread hilo = new Thread(() => showForms(new Form2()));
hilo.Start();
listThreads.Add(hilo);
abrir = false;
}
}
else{
for(int i=0; i<listThreads.Count; i++)
{
try
{
Invoke((MethodInvoker)delegate {
listForms[i].Close();
});
listThreads[i].Abort();
}
catch (ThreadAbortException)
{
}
}
}
}
List<Form2> listForms = new List<Form2>();
private void showForms(Form2 form)
{
listForms.Add(form);
form.ShowDialog();
}
You have a synchronize with the main thread UI conflict.
You must sync the call to any action on UI controls with the main thread.
You can use a BackgroundWorker.
Or this:
static public class SyncUIHelper
{
static public Thread MainThread { get; private set; }
// Must be called from the Program.Main or the Main Form constructor for example
static public void Initialize()
{
MainThread = Thread.CurrentThread;
}
static public void SyncUI(this Control control, Action action, bool wait = true)
{
if ( !Thread.CurrentThread.IsAlive ) throw new ThreadStateException();
Exception exception = null;
Semaphore semaphore = null;
Action processAction = () =>
{
try { action(); }
catch ( Exception except ) { exception = except; }
};
Action processActionWait = () =>
{
processAction();
if ( semaphore != null ) semaphore.Release();
};
if ( control != null
&& control.InvokeRequired
&& Thread.CurrentThread != MainThread )
{
if ( wait ) semaphore = new Semaphore(0, 1);
control.BeginInvoke(wait ? processActionWait : processAction);
if ( semaphore != null ) semaphore.WaitOne();
}
else
processAction();
if ( exception != null ) throw exception;
}
}
Usage:
this.SyncUI(listForms[i].Close /*, true or false to wait or not */);
And:
this.SyncUI(() => form.ShowDialog() /*, true or false to wait or not */);
With:
private void Form1_Load(object sender, EventArgs e)
{
SyncUIHelper.Initialize();
RunFileSystemWatcher();
}
You need to correct your code in FileSystemWatcher_Changed because it is buggy.
I have looked everywhere for the answer and I thought it would be simple to find but apparently not. I've heard about invoke but I have no idea how to use it or what it is.
Here is my code:
public void Thread1(object sender, EventArgs e)
{
this.button1.Enabled = false;
this.textBox2.Clear();
this.textBox3.Clear();
this.textBox4.Clear();
this.textBox6.Text = "£" + "0";
//Generate 3 random numbers
Stopwatch timer = new Stopwatch();
timer.Start();
this.Refresh();
//This is only part of this function
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart threadStart = new ThreadStart(() => Thread1(sender, e));
Thread newThread = new Thread(threadStart);
newThread.Start();
}
In background threads, use Invoke() on WinForms components to execute code on the UI thread:
this.Invoke( () => {
this.button1.Enabled = true;
this.textBox2.Text = "whatever";
} );
Documentation: https://msdn.microsoft.com/en-us/library/a1hetckb.aspx
I want to change wpf controls status after click button start.
The picture is what I want.
Following is my code
private bool _bWorking = false;
public delegate void UpdateStatusDelegate();
private void SetStatus(bool bEnable)
{
if (bEnable)
{
tbName.IsReadOnly = false;
barStatus.Visibility = Visibility.Hidden;
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
btnClose.IsEnabled = true;
}
else
{
tbName.IsReadOnly = true;
barStatus.Visibility = Visibility.Visible;
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnClose.IsEnabled = false;
}
}
internal void UpdateStatus()
{
SetStatus(true);
_bWorking = false;
}
private void ThreadFunc()
{
//for (; ; )
//{
// // do something here
// if (_bWorking == false)
// break;
//}
Thread.Sleep(500);
this.Dispatcher.Invoke(new UpdateStatusDelegate(UpdateStatus));
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
_bWorking = true;
SetStatus(false);
this.UpdateLayout();//this.InvalidateVisual();
try
{
Thread t = new Thread(new ThreadStart(() =>
{
ThreadFunc();
}));
t.IsBackground = true;
t.Name = "test status";
t.Start();
while (t.IsAlive)
{
// wait thread exit
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_bWorking = false;
SetStatus(true);
}
But actually after I click button start, the UI seems frozen, and the thread exited, the UI become normal.
my VS is VS2010.
Force a WPF control to refresh?
this post is not work for me.
edit summary:
add delegate void UpdateStatusDelegate() and function UpdateStatus() to my code
// wait thread exit
The whole point of a background thread is to not wait for it by blocking your UI thread. Don't.
Instead, have the thread notify your UI when it's done.
Try to use BackgroundWorker component it might help you. For more information check the MSDN article
workable code
public delegate void UpdateStatusDelegate();
private bool _bWorking = false;
private void SetStatus(bool bEnable)
{
if (bEnable)
{
tbName.IsReadOnly = false;
barStatus.Visibility = Visibility.Hidden;
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
btnClose.IsEnabled = true;
}
else
{
tbName.IsReadOnly = true;
barStatus.Visibility = Visibility.Visible;
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnClose.IsEnabled = false;
}
}
internal void UpdateStatus()
{
SetStatus(true);
}
private void ThreadFunc()
{
try
{
// use Stopwatch to simulate jobs
var watch = Stopwatch.StartNew();
for (; ; )
{
var elapsedMs = watch.ElapsedMilliseconds;
if (elapsedMs > 10000 // 10 seconds
|| _bWorking == false)
{
break;
}
}
watch.Stop();
this.Dispatcher.Invoke(new UpdateStatusDelegate(UpdateStatus));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
_bWorking = false;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
_bWorking = true;
SetStatus(false);
Thread t = new Thread(new ThreadStart(() =>
{
ThreadFunc();
}));
t.IsBackground = true;
t.Name = "test status";
t.Start();
//while (t.IsAlive)
{
// wait thread exit
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
_bWorking = false;
SetStatus(true);
}
This is not throwing me any error but after executing first thread it is not executing second thread. Am I doing anything wrong?
Below is my code:
My button click Function:
private void ImportBothButtonclick(object sender, EventArgs e)
{
// Get the currently selected manufacturer from the combo box
var selected = comboBox.SelectedItem;
// Do we have one?
if (selected != null)
{
// Extract the combo record
var val= (ComboBoxItem)selected;
// Do we have one?
if (val != null)
{
// yes
// Make this on a seperate thread so that the UI continues to work
Invoke(new System.Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
var thread = new Thread(DoFunction1);
thread.Start(val);
}));
Invoke(new System.Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3Enabled = false;
var thread = new Thread(DoFunction2);
thread.Start(val);
}));
}
}
}
Those Actions will not do a thing. The actions are Invoked on the same thread you're currently on.
The threads are currently running parallel. If you want those threads to run in serial but not on the gui thread, you could do something like this:
This is the NON-TASK version.
// not allowed on a non-gui thread.
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
new Thread(() =>
{
DoFunction1();
DoFunction2();
// execute this on the gui thread. (winforms)
this.Invoke(new Action( delegate
{
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
}));
}).Start();
If you want to run them parallel, but wait until they are done:
// not allowed on a non-gui thread.
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
new Thread(() =>
{
ManualResetEvent wait1 = new ManualResetEvent(false);
ManualResetEvent wait2 = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((state) =>
{
DoFunction1();
wait1.Set();
});
ThreadPool.QueueUserWorkItem((state) =>
{
DoFunction2();
wait2.Set();
});
ManualResetEvent.WaitAll(new WaitHandle[] { wait1, wait2 });
this.Invoke(new Action( delegate
{
// execute this on the gui thread. (winforms)
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
}));
}).Start();
But this can be more easy using tasks. Task Parallelism (Task Parallel Library) http://msdn.microsoft.com/en-us/library/dd537609.aspx
Please clarify what is the exact problem you observe?
According to what you have currently stated, the problem lies in "the second thread is not run after the first thread".
So, let me answer to this.
Your code is almost OK. You have overlooked one important thing: your code "new thread / thread.start()" actually do start a new thread, and then it does not wait for that thread to execute nor complete.
lines:
new thread(f1).Start()
new thread(f2).Start()
will not "run F1 on thread1, then run F2 on thread2". Instead, they will "begin to run F1 on thread1 and immediately begin to run F2 on thread2".
To have the F2 executed only after F1 has fully finished, you must somehow 'chain' the two together:
you can create simple "agenda" method and run it instead:
private void doAllTasks()
{
f1();
f2();
}
new thread(doAllTasks).Start()
you can try "chaining" them on the fly via lambdas, which effectively is the same as above:
new thread(() => { f1(); f2(); ).Start()
you can actually run them both immediatelly, but have the F2 join [wait] until the F1's thread ends
var th1 = new thread(f1);
var th2 = new thread(() => {th1.Join(); f2();} )
th1.Start();
th2.Start();
// note that this code is NOT perfect, there's some error handling to do here, etc..
or, you can try some nice and pretty wrappers for all of that like the TPL framework, as seen in Sheridan's answer.
And of course you must be careful with touching UI elements from inside what those other new thread's are running. Sheridan's answer already covers it via TPL ways. Manually, you'd have to use Invoke/BeginInvoke to bounce the UI-related code back to UI thread. In your current code you already have it, but at that place, it is not necessary, since the _Click handler method obviously already runs in UI thread.
Therefore, your current example can be reduced to i.e.:
private void ImportBothButtonclick(object sender, EventArgs e)
{
var selected = comboBox.SelectedItem;
if (selected != null)
{
var val= (ComboBoxItem)selected;
if (val != null)
{
// no Invoke needed, "button_click" handlers
// are already on UI thread, so touching UI things is OK
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
// starting a new thread also does not need Invoke
var thread = new Thread(DoAllFunctions);
thread.Start(val);
}
}
}
private void DoAllFunctions(object something)
{
DoFunction1(something);
DoFunction2(something);
// button1.Enabled = true; - cannot do it here because they are UI
// button2.Enabled = true; - and DoAll is run from other thread.
// button3.Enabled = true; - you must bounce that back to UI thread.
LetTheUIKnowJobsAreFinished(); // <- performed here
}
private void LetTheUIKnowJobsAreFinished()
{
Invoke(new Action(()=>{
button1.Enabled = true;
button2.Enabled = true;
button3.Enabled = true;
});
}
Also, as a final note, please go and look at the BackgroundWorker from System.ComponentModel. It has very nice set of events/callbacks that will make all the thread-crossing very easy.
(btw. let me say that again: this code is NOT usable. it is only a sketch. you will find typos, missing colons, missing try-catch-whatever etc!)
Maybe you could do something like this?:
Task.Factory.StartNew(new Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = false;
}), new CancellationToken(), TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).
ContinueWith(new Action(() => DoFunction1)).
ContinueWith(new Action(() =>
{
button1.Enabled = false;
button2.Enabled = false;
button3Enabled = false;
}), TaskScheduler.FromCurrentSynchronizationContext()).
ContinueWith(new Action(() => DoFunction2));