Today I got a Problem with a Deadlock in a App I developed.
In particular I am stopping a Window which started a Thread in the Background.
Problem? On Closing of the Window the Window closed, but the Process was still alive in the Background.
I thought about if the Thread itself blocks, but as a Background-declared Thread it should normaly shut down anyway.
But then I noticed that "Logic.DoSomething()" started it's own endless-Threads without declaring it as Background-Threads.
So why is a (non-Background) Thread in a (Background) Thread blocking the Process? Shouldn't it shutdown because it's "Parent" runs as Background-Thread?
And if not, why doesn't block the Parent-Thread itself?
public partial class MainWindow : Window
{
private Thread TheThread { get; set; }
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
Closed += MainWindow_Closed;
}
private void MainWindow_Loaded(object sender, EventArgs e)
{
// Create the (background-)thread and start it
TheThread = new Thread(() => Logic.DoSomething());
TheThread.IsBackground = true;
TheThread.Start();
}
private void MainWindow_Closed(object sender, EventArgs e)
{
// Close Thread if existent.
TheThread.Abort("Window was closed");
TheThread.Join(500);
}
}
PS: Please note, that I don't use a Worker-Object because I handle the Abort-Exception in the Thread normaly.
Edit: It is a theoretical Question for a Windows Presentation Foundation-Project (WPF) and not for an Windows Forms Project.
Besides of that the Basic-Question was NOT how to Start and Stop a Thread using e.g. CancellationToken. It was a Question why a Thread made by a Process doesn't kill the Process it called or is gettin stuck in the Join.
Hope this is clear enough.
Instead of calling Abort on the thread, which is never a good idea, communicate with the thread and indicate it should stop. Then wait for the thread to stop. A good means of communicating things like this are wait handles. In the following example I'll use a ManualResetEvent:
public partial class MainWindow : Window
{
private ManualResetEvent theThreadShouldStop;
private Thread TheThread { get; set; }
public MainWindow()
{
InitializeComponent();
// ??? This should be set through designer!!
Loaded += MainWindow_Loaded;
Closed += MainWindow_Closed;
theThreadShouldStop = new ManualResetEvent(false);
}
private void MainWindow_Loaded(object sender, EventArgs e)
{
// Create the (background-)thread and start it
TheThread = new Thread(() => Logic.DoSomething(theThreadShouldStop));
TheThread.IsBackground = true;
TheThread.Start();
}
private void MainWindow_Closed(object sender, EventArgs e)
{
// Close Thread if existent.
theThreadShouldStop.Set();
TheThread.Join();
theThreadShouldStop.Close();
}
}
Now the thread method needs to be cooperative, but as I don't know what the thread method does, I'll give an example of a common scenario where the thread runs in a loop that should be cancellable:
public class Logic
{
public void DoSomething(ManualResetEvent cancel)
{
while (!WantHandle.WaitOne(cancel, 1))
{
... Do stuff
}
}
}
Threads don't have "parents". There is no hierarchy. They don't know or care what thread started them. They're just threads, and the application ends when no non-background threads are running; that's all there is to it. Threads block until another thread ends when you ask them to by calling Join. They don't just do things on their own without you telling them to. They do exactly what you tell them to.
Related
I'm writing a simple game that uses timers from the system.threading namespace to simulate wait times for actions. My goal is to have the timer execute once every second for x amount of seconds. To achieve this I added a counter in the timer callback.
The problem is any code I place after invoking the DeliveryProgressChangedEvent event seems to get ignored. My counter is never incremented thus allowing the timer to run forever.
If I invoke the event after I increment the counter, everything works fine. Just nothing after invoking the event will execute. Rather than going the easy route I'd like to understand if not resolve this problem.
I did a fair bit of research into the system.threading timer object as well as events but wasn't able to find any information related to my issue.
I created a bare bones example of my project to demonstrate the issue below.
Game Class
class Game
{
private Timer _deliveryTimer;
private int _counter = 0;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game()
{
_deliveryTimer = new Timer(MakeDelivery);
}
public void StartDelivery()
{
_deliveryTimer.Change(0, 1000);
}
private void MakeDelivery(object state)
{
if (_counter == 5)
{
_deliveryTimer.Change(0, Timeout.Infinite);
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
}
}
Form Class
public partial class Form1 : Form
{
Game _game = new Game();
public Form1()
{
InitializeComponent();
_game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
_game.DeliveryCompletedEvent += onDeliveryCompleted;
pbDelivery.Maximum = 5;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
MessageBox.Show("Delivery Completed");
}
private void button1_Click(object sender, EventArgs e)
{
_game.StartDelivery();
}
}
EDIT
Just to clarify what I mean. Any code I put after DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty); will not execute. In my example ++_counter will not run. The event does fire and the onDeliveryProgressChanged handler does run.
The problem:
Using a System.Threading.Timer class, when the TimerCallback is called, events are raised, to notify the subscribers of the DeliveryProgressChangedEvent and DeliveryCompletedEvent of custom Game class of the progress of a procedure and the termination of it.
In the sample class, the subscriber (a Form class, here) updates an UI, settings the value of a ProgressBar control and also showing a MessageBox (used in the actual implementation of the class sample shown here).
It appears that after the first event is invoked:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
the line where the _counter should be increased is never reached, thus the code that inspects the _counter to set the Timer to a new value is never executed.
What happens:
The System.Threading.Timer is served by ThreadPool threads (more than one). Its callback is called on a thread other than the UI thread. The events invoked from the callback are also raised in a ThreadPool thread.
The code in the handler delegate, onDeliveryProgressChanged, is then run on the same Thread.
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
When the MessageBox is shown - it's a Modal Window - it blocks the Thread from where it's run, as usual. The code following the line where the event is invoked is never reached, so _counter is never increased:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
The System.Threading.Timer can be served by more than one thread. I'm just quoting the Docs on this point, it's quite straightforward:
The callback method executed by the timer should be reentrant, because
it is called on ThreadPool threads. The callback can be executed
simultaneously on two thread pool threads if the timer interval is
less than the time required to execute the callback, or if all thread
pool threads are in use and the callback is queued multiple times.
What happens, in practice, is that while the Thread where the CallBack is executed, is blocked by the MessageBox, this doesn't stop the Timer from executing the CallBack from another thread: a new MessageBox is shown when the event is invoked and it keeps on running until it has resources.
The MessageBox has no Owner. When a MessageBox is shown without specifying the Owner, its class uses GetActiveWindow() to find an Owner for the MessageBox Window. This function tries to return the handle of the active window attached to the calling thread's message queue. But the thread from which the MessageBox is run has no active Window, as a consequence, the Owner is the Desktop (actually, IntPtr.Zero here).
This can be manually verified by activating (clicking on) the Form where the MessageBox is called: the MessageBox Window will disappear under the Form, since it's not owned by it.
How to solve:
Of course, using another Timer. The System.Windows.Forms.Timer (WinForms) or the DispatcherTimer (WPF) are the natural substitutes. Their events are raised in the UI Thread.
► The code presented here is just a WinForms implementation made to
reproduce a problem, hence these may not apply to all contexts.
Use a System.Timers.Timer: the SynchronizingObject property provides means to marshal the events back to the Thread that created the current class instance (same consideration in relation to the concrete implementation context).
Generate an AsyncOperation using the AsyncOperationManager.CreateOperation() method, then use a SendOrPostCallback delegate to let the AsyncOperation call the SynchronizationContext.Post() method (classic BackGroundWorker style).
BeginInvoke() the MessageBox, attaching it to the UI Thread SynchronizationContext. E.g.,:
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
Now the MessageBox is owned by the Form and it will behave as usual. The ThreadPool thread is free to continue: the Modal Window is synched with the UI Thread.
Avoid using a MessageBox for this kind of notifications, since it's really annoying :) There are many other ways to notify a User of status changes. The MessageBox is probably the less thoughtful.
To make them work as intended, without changing the current implementation, the Game and Form1 classes can be refactored like this:
class Game
{
private System.Threading.Timer deliveryTimer = null;
private int counter;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game(int eventsCount) { counter = eventsCount; }
public void StartDelivery() {
deliveryTimer = new System.Threading.Timer(MakeDelivery);
deliveryTimer.Change(1000, 1000);
}
public void StopDelivery() {
deliveryTimer?.Dispose();
deliveryTimer = null;
}
private void MakeDelivery(object state) {
if (deliveryTimer is null) return;
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
counter -= 1;
if (counter == 0) {
deliveryTimer?.Dispose();
deliveryTimer = null;
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
}
}
public partial class Form1 : Form
{
Game game = null;
public Form1() {
InitializeComponent();
pbDelivery.Maximum = 5;
game = new Game(pbDelivery.Maximum);
game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
game.DeliveryCompletedEvent += onDeliveryCompleted;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
this.BeginInvoke(new MethodInvoker(() => {
pbDelivery.Increment(1);
// This MessageBox is used to test the progression of the events and
// to verify that the Dialog is now modal to the owner Form.
// Of course it's not used in an actual implentation.
MessageBox.Show(this, "Delivery In progress");
}));
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
}
private void button1_Click(object sender, EventArgs e)
{
game.StartDelivery();
}
}
Good information. The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times.
What is the proper procedure to alert a running thread to stop what it's doing and return before exiting the application?
protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;
public Form1()
{
InitializeComponent();
T = new Thread(ThreadFunc);
T.Start();
}
private void ThreadFunc()
{
while (ThreadRunning)
{
// Do stuff
Thread.Sleep(40);
}
mre.Set();
}
private void ExitButton_Click(object sender, EventArgs e)
{
ThreadRunning = false;
mre.WaitOne();
mre.Close();
Application.Exit();
}
Initially I had my code setup like the above. My thinking for how exit properly is as follows:
Set ThreadRunning = false so that the next time thread T checks that variable it knows to stop.
Call mre.WaitOne() to wait for thread T to say it's actually done via it calling mre.Set().
If so, then unblock and continue, dispose of mre (mre.Close()) and exit.
For some reason the above setup sometimes fails after the exit button is clicked and the whole form becomes inactive.
My new setup is below but doesn't seem entirely correct to me, for instance mre.Set() isn't going to wait for anything and Application.Exit() is immediately after it. I'm just waiting for it to fail like before, but so far it hasn't.
protected Thread T;
protected static ManualResetEvent mre = new ManualResetEvent(false);
protected static bool ThreadRunning = true;
public Form1()
{
InitializeComponent();
T = new Thread(ThreadFunc);
T.Start();
}
private void ThreadFunc()
{
while (ThreadRunning)
{
// Do stuff
Thread.Sleep(40);
}
mre.WaitOne();
mre.Close();
}
private void ExitButton_Click(object sender, EventArgs e)
{
ThreadRunning = false;
mre.Set();
Application.Exit();
}
UI hangs because you blocks UI thread with mre.WaitOne();.
If you need to wait until thread exits, you can use its IsAlive property and process application messages, you don't need application events for that:
while(_t.IsAlive)
Application.DoEvents();
There are 2 thread cancelation aproaches:
cooperative - code that is executed by thread knows that it could be cancelled and cancellation handled gracefuly, that's what you try to do here.
imperative - force thread to stop - call Thread.Abort or Interrupt, don't use that.
As #HansPassant mentioned, bool is not the best option because that compiler may optimize that and bool value could be cached and its change may not be handled by looping thread. You need to make it at least volatile or just refactor code to use CancellationSource.
Given what your thread is doing, perhaps BackgroundWorker, Timer or Producer/Consumer pattern is a better alternative to Thread, but I have too little context to recommend anything. Also it works well only if you have only 1 instance of Form1 in the application, if you have multiform application and user can open several Form1 forms, you'll have problems.
General recommendation, if you can work with instance level fields, please do that, don't use static.
Waiting for 40 msec between doing stuff creates no problems, but what if you had to wait 5 sec or more? Then canceling between each waiting would be problematic, and the right thing to do would be to cancel the awaiting itself. It is fairly easy to do it actually. Just replace Thread.Sleep(40) with Task.Delay(40, token).Wait() where token is a CancellationToken.
class Form1 : Form
{
protected readonly CancellationTokenSource _cts;
protected readonly Thread _thread;
public Form1()
{
InitializeComponent();
_cts = new CancellationTokenSource();
_thread = new Thread(ThreadFunc);
_thread.Start();
}
private void ThreadFunc()
{
try
{
while (true)
{
// Do stuff here
Task.Delay(40, _cts.Token).GetAwaiter().GetResult();
}
}
catch (OperationCanceledException)
{
// Ignore cancellation exception
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
_cts.Cancel();
this.Visible = false; // Hide the form before blocking the UI
_thread.Join(5000); // Wait the thread to finish, but no more than 5 sec
this.Close();
}
}
Personally I would prefer to do the background job using a Task instead of a Thread, because it is more easily awaited without blocking the UI. This task would run lazily using thread-pool threads. The drawback is that the stuff that runs every 40 msec would not always run in the same thread, so I could have thread-safety issues to resolve.
class Form1 : Form
{
protected readonly CancellationTokenSource _cts;
protected readonly Task _task;
public Form1()
{
InitializeComponent();
_cts = new CancellationTokenSource();
_task = Task.Run(TaskFunc);
this.FormClosing += Form_FormClosing;
}
private async Task TaskFunc()
{
try
{
while (true)
{
// Do async stuff here, using _cts.Token if possible
// The stuff will run in thread-pool threads
await Task.Delay(40, _cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Ignore cancellation exception
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
this.Close();
}
private async void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (_task == null || _task.IsCompleted) return;
e.Cancel = true;
_cts.Cancel();
this.Visible = false; // Or give feedback that the form is closing
var completedTask = await Task.WhenAny(_task, Task.Delay(5000));
if (completedTask != _task) Debug.WriteLine("Task refuses to die");
_task = null;
await Task.Yield(); // To ensure that Close won't be called synchronously
this.Close(); // After await we are back in the UI thread
}
}
I'm getting some troubles with my Winforms C# app.
I wish to make form named Popup closing after some operations in main thread are done. The problem is an exception caused by cross-thread form closing.
private void loginButton_Click(object sender, EventArgs e)
{
LoginProcess.Start(); // Running Form.show() in new thread
ActiveAcc.IsValid = false;
ActiveAcc.Username = userBox.Text;
try
{
LoginCheck(userBox.Text, passBox.Text);
}
catch (IOException)
{
MessageBox.Show("..");
return;
}
catch (SocketException)
{
MessageBox.Show("..");
return;
}
if (ActiveAcc.IsValid)
{
MessageBox.Show("..");
Close();
}
else
{
Popup.Close(); // Error caused by closing form from different thread
MessageBox.Show("");
}
}
public Login() // 'Main' form constructor
{
InitializeComponent();
ActiveAcc = new Account();
Popup = new LoginWaiter();
LoginProcess = new Thread(Popup.Show); //Popup is an ordinary Form
}
I've been trying to use various tools such as LoginProcess.Abort() or Popup.Dispose() to make it work properly, but even if app is working on runtime environment its still unstable due to Exceptions which are thrown.
I would be grateful for any help, and I am sorry for ambiguities in issue describing.
Why don't you let the UI thread do UI stuff like opening and closing Forms, and spawn the other thread (or background worker, or async task) to do the other stuff?
IMO, having other threads attempt to interact with elements on the UI thread (e.g., have a background thread directly set the text of a label or some such) is asking for heartache.
If you simply must keep your code as is, here is a fairly simple thing you could do. In Popup, add a static bool that defaults to true. Also in Popup, add a timer task that once every X milliseconds checks the status of that boolean. If it finds that the value has been set to false, let Popup tell itself to close within that timer tick.
I'm not crazy about this design, but it could look something like:
public partial class Popup : Form
{
public static bool StayVisible { get; set; }
private System.Windows.Forms.Timer timer1;
public Popup()
{
StayVisible = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!StayVisible) this.Close();
}
}
Then, from another thread, when you want Popup to close, call
Popup.StayVisible = false;
Better yet, you would fire an event that Popup would receive so that it could close itself. Since you intend to use multiple threads, you'll have to deal with raising events cross-thread.
So I've got an application that employs a filesystemWatcher and triggers an event just fine. The FSW will trigger a bunch of times pretty close together. I want to create a function that triggers say an hour after the last time the FSW was triggered.
I first tried using a backgroundworker: (All code is shortened for clarity)
namespace Devo
{
public partial class Form1 : Form
{
BackgroundWorker bw = new BackgroundWorker();
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (bw.IsBusy)
{
bw.CancelAsync(); //this is to, in a way, reset the timer for the delayed method.
}
//do a lot of stuff
bw.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
while(sw.ElapsedMilliseconds < 180000)
{
if (bw.CancellationPending == true)
{
sw.Stop();
sw.Reset();
e.Cancel = true;
return;
}
}
sw.Stop();
sw.Reset();
DelayedMethod();
}
}
}
This didn't work as the second time bw.RunWorkerAsync() was called it was apparently busy, even though the call to bw.CancelAsync().
My next attempt involved a regular thread as I read somewhere on SO (can't find the link now) that one could not "restart" a backgroundWorker as I am trying to do.
The thread attemp is nearly identical but I thought I'd try in since there might be some constraints within the backgroundWorker that is not present in a regular thread. I thought.
namespace Devo
{
public partial class Form1 : Form
{
Thread PWC_counter_thread = new Thread(PWC_Counter);
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (PWC_counter_thread.IsAlive)
PWC_counter_thread.Abort();
//do a lot of stuff
PWC_counter_thread.Start();
}
static void PWC_Counter()
{
Thread.Sleep(180000);
DelayedMethod();
}
}
}
But this gave me the same error. On the second call to PWC_counter_thread.Start() is was busy.
I'm assuming that a race condition is not present as the second thread waits for, in this example, 3 minutes, and the initial FSW method takes a good full second to execute, therefore I believe that the call to .Abort() and .CancelAsync() both are done before their respective methods are completed.
Now for the questions:
Is it possible to restart a thread in the fashion I am trying? If so, what am I doing wrong?
Should I delay my method call in another way? If so, tips?
EDIT/UPDATE/SOLUTION
I never got starting and stopping a thread to work as I wanted so I found another solution to my situation.
The situation was that I had a second thread that worked as a sort of timer where it would call a method after a set amount of time. My first thread did some work and upon finishing it would start the second thread. If the first thread got fired up again before the timer-thread had finished it was supposed to kill the thread and restart it.
This proved, for me, to be difficult to get the way I wanted. So I instead took another approach towards my wanted end result. Instead of restarting the thread I simply restarted the stopwatch that my second thread was using as a counter. This gave me the result I wanted. It's probably bad practice but it works.
In your BackgroundWorker example you probably have an issue with racing. CancelAsync(), as its name implies, is an asynchronious call, meaning that BackgroundWorker does not stop working immediately and it might still work when try to restart it. To avoid that, you should subscribe to RunWorkerCompleted event and wait for it to fire before calling bw.RunWorkerAsync(); again. For example:
public Form1()
{
bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnCompleted;
}
private BackgroundWorker bw;
private ManualResetEvent completed = new ManualResetEvent(false);
private void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
{
completed.Set();
}
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
if (bw.IsBusy)
{
bw.CancelAsync();
completed.WaitOne();
}
//do a lot of stuff
completed.Reset();
bw.RunWorkerAsync();
}
You have multiple issues with your Thread-based example.
You should never call Thread.Abort(). Instead, you should implement a cancellation mechanism, similar to that of BackgroundWorker. Make a bool field (_isCancelled or something) and check it periodically in thread delegate.
You can not reuse a Thread object. You should always create a new one.
You would be best off encapsulating this in a class, and use a System.Threading.Timer to detect the inactivity.
Here's an example I put together. The idea is that you create an InactivityDetector with the appropriate inactivity threshold (an hour in your case) and a callback method that will be called when that period of inactivity is exceeded.
You have to call InactivityDetector.RegisterActivity() whenever activity is detected (e.g. in your case a file creation is detected).
Once the inactivity callback has been issued, it will not be called again until RegisterActivity() has been called again (this prevents multiple callbacks for the same period of extended inactivity).
Your code would pass DelayedMethod for the inactivity Action delegate.
Note that the callback is on a separate thread!
(Also note that I didn't put in any parameter validation, to keep the code shorter.)
using System;
using System.Threading;
namespace ConsoleApp1
{
sealed class Program
{
void test()
{
using (var inactivityDetector = new InactivityDetector(TimeSpan.FromSeconds(2), inactivityDetected))
{
for (int loop = 0; loop < 3; ++loop)
{
Console.WriteLine("Keeping busy once a second for 5 seconds.");
for (int i = 0; i < 5; ++i)
{
Thread.Sleep(1000);
Console.WriteLine("Registering activity");
inactivityDetector.RegisterActivity();
}
Console.WriteLine("Entering 3 second inactivity");
Thread.Sleep(3000);
inactivityDetector.RegisterActivity();
}
}
}
static void inactivityDetected()
{
Console.WriteLine("Inactivity detected.");
}
static void Main(string[] args)
{
new Program().test();
}
}
public sealed class InactivityDetector: IDisposable
{
public InactivityDetector(TimeSpan inactivityThreshold, Action onInactivity)
{
_inactivityThreshold = inactivityThreshold;
_onInactivity = onInactivity;
_timer = new Timer(timerCallback, null, (int)inactivityThreshold.TotalMilliseconds, -1);
}
public void RegisterActivity()
{
_timer.Change(-1, -1);
_timer.Change((int)_inactivityThreshold.TotalMilliseconds, -1);
}
private void timerCallback(object state)
{
_timer.Change(-1, -1);
_onInactivity();
}
public void Dispose()
{
_timer.Dispose();
}
private readonly TimeSpan _inactivityThreshold;
private readonly Action _onInactivity;
private readonly Timer _timer;
}
}
My application fetches data from a live feed, processes it and displays the results. This data is updated every 5 seconds. In the Load event of Main form I've created a thread to show the splash screen which is shown until the first data cycle is run .
The data fetching and processing thread (RecieverThread) calls RecieveFeed. The isue I'm facing is that form2 which displays data fetched in RecieveFeed is shown before the first cycle is run completely. How do I ensure that form2 is loaded only after the first cycle has completed fetching data.
Code in the Main form:
private void frmMain_Load(object sender, EventArgs e)
{
Hide();
// Create a new thread from which to start the splash screen form
Thread splashThread = new Thread(new ThreadStart(StartSplash));
splashThread.Start();
//Thread to call the live feed engine. This thread will run for the duration of application
ReceiverThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReceiveFeed));
ReceiverThread.Start();
frmSecondForm form2 = new frmSecondForm();
form2.MdiParent = this;
form2.WindowState = FormWindowState.Maximized;
Show();
form2.Show();
}
public frmRaceRace()
{
InitializeComponent();
this.splash = new SplashScreen();
}
private void StartSplash()
{
splash.Show();
while (!done)
{
Application.DoEvents();
}
splash.Close();
this.splash.Dispose();
}
private void ReceiveFeed()
{
while (!StopReceivingData)
{
foreach (...)
{
//Fetches data from live engine
DLLImportClass.GetData1();
//Manipulates and stores the data fetched in datatables
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { StoreData(); }))
rowsProcessed++;
if (!done)
{
this.splash.UpdateProgress(100 * rowsProcessed / totalRows);
}
}
done = true;
Thread.Sleep(5000);
}
}
I think what you need to use here is System.Threading.AutoResetEvent. Basically, add a member of this to your form class:
private AutoResetEvent waitEvent_ = new AutoResetEvent(false); // create unininitialized
After showing your splash, you want to wait for this event to be signalled:
private void StartSplash()
{
splash.Show();
// this will time out after 10 seconds. Use WaitOne() to wait indefinitely.
if(waitEvent_.WaitOne(10000))
{
// WaitOne() returns true if the event was signalled.
}
} // eo StartSplash
Finally, in your processing function, when you're done, simply call:
waitEvent_.Set();
Looks like you've got some race conditions in your code.
When doing threading with WinForms and most (if not all) UI frameworks, you can ONLY access the UI objects (forms and controls) from a single thread.
All other threads can only access that thread using .InvokeRequired() and .BeginInvoke(). These calls can be used to run a delegate in the UI thread. See:
{REDACTED: StackOverflow will only allow me to post 1 hyperlink. Google these}
There is a builtin shortcut for this in the BackgroundWorker class.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Simply do this (Psuedocode):
public void StartSplash()
{
Splash.Show();
BackgroundWorker bgw = new BackgroundWorker();
// set up bgw Delegates
bgw.RunWorkerAsync();
}
public void bgw_DoWork( ... etc
{
// do stuff in background thread
// you cannot touch the UI from here
}
public void bgw_RunWorkerCompleted( ... etc
{
Splash.close();
// read data from background thread
this.show(); // and other stuff
}
Now, you're guaranteed not to close the SplashScreen and not to start the main window before your data is delivered.
Other considerations: you'll probably need to use locks to secure the data you might access in the background thread. You should never access data in more than 1 thread without locking it.
Change your frmMain_Load to this:
private void frmMain_Load(object sender, EventArgs e)
{
Hide();
//Thread to call the live feed engine. This thread will run for the duration of application
ReceiverThread = new System.Threading.Thread(new System.Threading.ThreadStart(ReceiveFeed));
ReceiverThread.Start();
frmSecondForm form2 = new frmSecondForm();
form2.MdiParent = this;
form2.WindowState = FormWindowState.Maximized;
StartSplash();
Show();
form2.Show();
}