|
Some times you just have to abort a thread.
|
|
|
|
|
You can close the form all you want so long as your WMI retrieval code doesn't update a control on the form. Your code should be generating a liost of applications and populating a collection with that data, NOT a control on a form. This way, you've completely disconnected the data from the UI.
Your code can supply an event to signal the form code that the collection has been completely populated, then the form can bind the display control to that data collection. When the form closes, it can remove the handler it registered, so, when the event tries to fire, it sees that it has no subscribers and doesn't do anything.
In other words, you can leave the data collection code running and not worry about killing the thread and possibly screwing up WMI provider in the process.
|
|
|
|
|
Well this is "kind of" how I did it. I populated a List collection with the data. Once it finished it then ran a foreach on the List and then populated the controls. BUT I did this all within the spawned thread and not the main thread.
So you are saying populate a global List and somehow tell the main thread it is done then when the main thread sees that my spawned thread is completed the main thread then loops through teh collection to populate the data.
Right? I may need an short example to see this
|
|
|
|
|
I posted what I thought you meant below but was trying it out.. calilng that event from that thread puts it on the spawned thread.. Do you happen to know an example of what you mean?
|
|
|
|
|
Dave Kreskowiak wrote: leave the data collection code running and not worry about killing the thread
But what if the user is trying to exit the application, not just the form? Perhaps he clicked the icon by mistake and now just wants to get out without gathering any information? Perhaps even log out and/or shutdown the system? Will you make the user wait for WMI to return unneeded data rather than simply abort the thread? Is WMI special? What if it were just a lengthy database query?
|
|
|
|
|
Well even if I called a abort on a thread will it abort during the call to WMI or wait for it to finish? The whole application is to retrieve services ( and their status ), installed applications with MSI, and other information like the type of drives it has.
So what you are suggesting is to cancel a form close then abort the threads? How do you know when the threads are fully aborted to where it is safe to close the form?
I'm a little new to using threads like this, but creating a new thread (not as background) will run even if the application is closed correct? All I want to do is not make a user wait (or think he/she is waiting) to close a form because WMI is finishing, but WMI doesn't really come with a "cancel" method. Users don't like to wait lol
Edit:
If I'm correct WMI will always end. It will either timeout, throw an error (which I have it just returning the error message by writing it in the eventlog), or return the actual data)
|
|
|
|
|
Use Join or check IsAlive?
Jacob Dixon wrote: Users don't like to wait
Especially if they don't need the thing they don't want to wait for.
Jacob Dixon wrote: cancel a form close then abort the threads
No. And don't make the form depend on threads that way.
|
|
|
|
|
I'm not really sure how to do this without "making the form depend on the threads". I have to use threads to get this information or the GUI will just lock up. Not letting the user close the form because the thread is still working is not feasible.
What you are suggesting is to check if a thread is alive on FormClosing (how else do you know when a user is closing a form), then if it is alive call a Thread.Abort(). I am not sure how threads abort.. so if it is in the middle of the WMI call does it ABORT then or wait until the WMI is finished and then abort?
If I join it with the main thread it will lock up the GUI right?
|
|
|
|
|
Jacob Dixon wrote: check if a thread is alive on FormClosing
No, the form shouldn't know about that thread at all -- it's within the class that handles the WMI calls -- just allow the form to close. The canceling/aborting of the WMI call is separate.
If I understand Dave's suggestion correctly... write a class that
0) Handles WMI requests
1) Holds the retrieved data
2) Raises an event to say that the data is available
In your Main... instantiate the WMI class, and pass a reference to it to the form (perhaps in the constructor).
The form stores the reference and attaches a handler.
When the user requests data, the form passes the request to the WMI class.
The WMI class performs the request on a thread.
When the data is ready it raises the event.
The form then does its thing.
When the form closes, it detatches its handler.
Then, if nothing else is using the WMI instance, that instance gets disposed -- it may have to abort a thread if it was busy at the time.
Something like:
using ( WMI wmi = new WMI() )
{
Application.Run ( new MyForm ( WMI ) ) ;
}
The form shouldn't know anything about what WMI does.
|
|
|
|
|
Jacob Dixon wrote: Well even if I called a abort on a thread will it abort during the call to WMI or wait for it to finish?
That's why I said don't worry about aborting it. The behavior of WMI is undefined if the caller is killed off before the WMI call returns. At least, i couldn't find definitive documentation on what the behavior is anyway.
Jacob Dixon wrote: I'm a little new to using threads like this, but creating a new thread (not as background) will run even if the application is closed correct?
If the thread is not tagged as background, the application will not close. Background threads do not prevent the app from closing.
|
|
|
|
|
I agree with Dave. You should set up a class to do the work for you, and when it is complete, it should fire an event that returns the values it found.
As an example, I have a program that has to extract the column information from an OLAP data cube. This process can take a long time, but I need the information to fill out the different parts of the form. But, sometimes, the user has finished filling out the form before the entire cube was loaded, or they decide to quit. So, on the form close, the first thing I do is to hide the form. As far as the user is concerned, the form is hidden.
Then, I send the call to the class to tell it to abort. The class looks for that abort message periodically as its working, and if it gets it, it will quit loading. Then, in the Form_Close method, I hook the BackgroundWorker.RunWorkerCompleted Event. When that event fires, I know that the process has completed, and I can dispose of the thread and form safely.
In your case, you can't tell it to prematurely stop, but you can let it finish loading in the background, but still hide the form from the user so that they think it is closed.
|
|
|
|
|
Well see I can't abort WMI.
ALL I can do is basically tell my code NOT to write the data to form if it sees the Handle isn't there. Now on the events do you mean something like this:
MAIN THREAD:
public delegate void UserRetrievalDelegate();
public event UserRetrievalDelegate UserRetrievalFinished;
private void frmMain_Load(object sender, EventArgs e)
{
UserRetrievalFinished += new UserRetrievalDelegate(frmMain_UserRetrievalFinished);
}
void frmMain_UserRetrievalFinished()
{
PopulateUsers();
}
USER RETRIEVAL THREAD (Not Main thread):
private void FillUsers()
{
users = new List<User>();
LDAP ldap = Login();
if (ldap != null)
{
PrincipalSearchResult<Principal> usrs = ldap.AllADUsers();
foreach (Principal principal in usrs)
{
User temp = ldap.GetUserInfo(principal.Name, "name");
users.Add(temp);
temp = null;
}
UserRetrievalFinished();
}
}
So the thread fires the event which calls my populateusers method from the main thread?
|
|
|
|
|
Nope.
The way you have it, PopulateUsers is not "from the main thread". It will be executed by the caller, on the caller's thread, as always. You need a delegate and a Control.Invoke to force the method to be called on the main thread; just moving some code from one source location to another cannot solve this.
You may want to read this[^]; you do not really need Control.InvokeRequired here, as you are certain the caller is some other thread.
|
|
|
|
|
Uhm. I see what you are saying also.... but how do I do that if the main thread has been disposed of? Basically in my spawned thread I need to double check and make sure the main thread is not disposed of.. if it is then basically don't try to write anything to it (because you can't), and if it is there then write the data to it
|
|
|
|
|
You seem to be very confused; a .NET program consists of two things: objects and threads;
objects contain code and data however they don't do a thing (they are the cookbook and the ingredients); threads execute code and operate on data (like chefs reading cookbooks and using ingredients), so it is threads that give live to objects (make soup).
If it is your Form that is to display the results of a long-wielding action (which you therefore have relegated to another thread), then you need Invoke() to pass the action back to the main thread (the thread is still alive, all that could have happened is that your Form got closed, disposed, and maybe collected); if your Form no longer exists, then you must prevent the Invoke from happening, so use a boolean flag, or set the delegate to null, or whatever other way you choose to tell the helper thread the results are no longer wanted.
|
|
|
|
|
Oh I understand I must invoke. I also read the article you have sent. So I know I must invoke controls that want to display data from another thread. What I didn't know was how to basically tell the thread NOT to invoke if the main thread wasn't here anymore.
if your Form no longer exists, then you must prevent the Invoke from happening
That is what I'm trying to figure out.. how to tell the form doesn't exist anymore and just not write anything. I just looked and was wondering if this.IsDisposed was a way to make it work
|
|
|
|
|
simplest is using the delegate itself: have the form set up the delegate, and when it decides to go away (you might use its finalizer, I would prefer a real event though, maybe FormClosing), clear the delegate; in the relegated code, check the delegate isn't null.
|
|
|
|
|
What is the harm of this:
private void GetServices()
{
WMI_Commands wmi = new WMI_Commands(Username, Password,
System.Management.ImpersonationLevel.Impersonate, System.Management.AuthenticationLevel.Default);
List<Services> services = wmi.GetServices(NetBios);
if (!this.IsDisposed)
{
foreach (Services s in services)
{
AddServices(s);
}
SetPBVisible("pbservices", false);
}
services = null;
}
I'm testing it right now... (process still running)
Basically I check and make sure that the form isn't disposed.. if it IS then I basically do nothing.. no need to write anything out, but if IsDisposed comes back false then I write it out.
|
|
|
|
|
With the original code you showed, I would normally put it into its own class, but if you didn't something like this:
private delegate void ApplicationsLoadedDel(List<Product> apps);
private event ApplicationsLoadedDel ApplicationsLoaded;
private void frmMain_Load(object sender, EventArgs e)
{
appThread = new Thread(new ThreadStart(GetApplications));
appThread.IsBackground = true;
this.ApplicationsLoaded += AddApplications;
appThread.Start();
}
private void LoadApplications()
{
WMI_Commands wmi = new WMI_Commands(Username, Password,
System.Management.ImpersonationLevel.Impersonate,
System.Management.AuthenticationLevel.Default);
List<Product> products = wmi.InstalledApplications(NetBios);
ApplicationsLoaded(products);
}
Delegate void AddApplicationsDel(List<Product> products)
private void AddApplications(List<Product> products)
{
if (this.InvokeRequired)
this.Invoke(new AddApplciationsDel(Address of AddApplications),
new Object() {products});
foreach (Product p in products)
{
AddApplication(p);
}
products = null;
SetPBVisible("pbapplications", false);
}
|
|
|
|
|
Uhm...
I see what you are doing.. but how does that solve the problem with a user closing the thread before its finished? Because that is somewhat like I thought I had it but the problem is it would still try to write to the form even when it was gone.
Calling ApplicationLoaded from the thread doesn't that not make it on the main thread? Which would end up with the thread still trying to write to a form that isn't there?
|
|
|
|
|
You could set up your Form_close like this:
public void form_close(Object sender, FormClosingEventArgs e)
{
this.Hide();
this.ApplicationsLoaded -= AddApplications;
this.ApplicationsLoaded =+ CloseFormOnCompletion;
}
public void CloseFormOnCompletion(List<Product> products)
{
this.Close();
}
So, the thread will continue to run, but nothing will be written to the form because you've changed what happens when the event is fired off.
|
|
|
|
|
Ah that is a pretty good idea to.. but I think just a simple MyDelegate != null would be easier?
|
|
|
|
|
Solution from all of your guys help:
I have tested this a couple times and it has yet to throw any errors on closing the form early. Still have plenty more testing to do though.
I create my event for when the applications have been loaded and start my threads
private delegate void ApplicationsLoadedDelegate(List<Product> products);
private event ApplicationsLoadedDelegate ApplicationsLoaded;
private void frmCompInfo_Load(object sender, EventArgs e)
{
ApplicationsLoaded += new ApplicationsLoadedDelegate(frmCompInfo_ApplicationsLoaded);
ServicesLoaded += new ServicesLoadedDelegate(frmCompInfo_ServicesLoaded);
InfoLoaded += new InfoLoadedDelegate(frmCompInfo_InfoLoaded);
appThread = new Thread(new ThreadStart(GetApplications));
appThread.Start();
driveThread = new Thread(new ThreadStart(GetInfo));
driveThread.Start();
serviceThread = new Thread(new ThreadStart(GetServices));
serviceThread.Start();
}
This is what I call which makes the call to the WMI DLL file I created for querying the data of a remote computer. It will return a List of my custom structure <product>. Before I do anything else with this I check and make sure that my ApplicationsLoaded is not null. If it is null that means the form has been closed. I do the invoke required blah blah blah because of another thread. I know you suggested not doing it, but I might call this from the main thread also (for the future). Plus the article you sent me the author was really fond of sticking to this technique.
private void GetApplications()
{
WMI_Commands wmi = new WMI_Commands(Username, Password,
System.Management.ImpersonationLevel.Impersonate, System.Management.AuthenticationLevel.Default);
List<Product> products = wmi.InstalledApplications(NetBios);
if (ApplicationsLoaded != null)
ApplicationsLoaded(products);
products = null;
}
private void frmCompInfo_ApplicationsLoaded(List<Product> products)
{
if (lstApplications.InvokeRequired)
lstApplications.Invoke(new ApplicationsLoadedDelegate(ApplicationsLoaded), new object[] { products });
else
{
lstApplications.BeginUpdate();
foreach (Product p in products)
{
ListViewItem item = new ListViewItem();
item.Text = p.Name;
item.SubItems.Add(p.Version);
item.SubItems.Add(p.Vendor);
item.SubItems.Add(p.InstallLocation);
lstApplications.Items.Add(item);
}
lstApplications.EndUpdate();
SetPBVisible(pbApplications, false);
}
}
On form closing I unload all of the events.
private void frmCompInfo_FormClosing(object sender, FormClosingEventArgs e)
{
ApplicationsLoaded -= frmCompInfo_ApplicationsLoaded;
ServicesLoaded -= frmCompInfo_ServicesLoaded;
InfoLoaded -= frmCompInfo_InfoLoaded;
}
I've taken a couple of your suggestions and kind of compiled them together I think. Like I said I have tested this only a couple times by loading the form and watching it start the thread and closing the form before the thread was completed. I never saw any kind of error messages at all. (which is what I wanted)
|
|
|
|
|
You posted whilst I was composing the message below! Hope it helps anyway
DaveIf this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. (Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
|
|
|
|
|
Reading through the messages above, you don't seem to have grasped the 'unsubscribe from event' way of dealing with this.
If you make sure that your thread updates the UI only via an event then by unsubscribing to that event, the UI update code will not get called! All the InvokeRequired stuff should be in the event consumer's handler method.
It's not clear whether you wish to let the thread complete naturally after the application closes or terminate it immediately. By setting IsBackground to true , it will terminate. Not setting (or setting to false ) will allow it to continue.
Here's some demo code which may help explain!
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows.Forms;
namespace ThreadingDemo
{
public partial class FormMain : Form
{
MyThreadedClass myThreadedClass;
public FormMain()
{
InitializeComponent();
myThreadedClass = new MyThreadedClass();
myThreadedClass.GotDataCompleted += myThreadedClass_GotDataCompleted;
FormClosing += FormMain_FormClosing;
Shown += new EventHandler(FormMain_Shown);
}
void FormMain_Shown(object sender, EventArgs e)
{
}
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
myThreadedClass.GotDataCompleted -= myThreadedClass_GotDataCompleted;
}
private void myThreadedClass_GotDataCompleted(object sender, GotDataCompletedEventArgs e)
{
if (InvokeRequired)
Invoke(new MethodInvoker(delegate { myThreadedClass_GotDataCompleted(sender, e); }));
else
{
foreach (string item in e.Data)
Text = item;
}
}
private void RunThread()
{
myThreadedClass.Begin();
}
private void RunThreadAsBackground()
{
myThreadedClass.SetAsBackground();
myThreadedClass.Begin();
}
}
public class MyThreadedClass
{
public event EventHandler<GotDataCompletedEventArgs> GotDataCompleted;
private Thread thread;
public MyThreadedClass()
{
thread = new Thread(new ThreadStart(Start));
}
public void Begin()
{
thread.Start();
}
protected virtual void OnGotDataCompleted(GotDataCompletedEventArgs e)
{
EventHandler<GotDataCompletedEventArgs> eh = GotDataCompleted;
if (eh != null)
eh(this, e);
}
private void Start()
{
GotDataCompletedEventArgs e = new GotDataCompletedEventArgs();
e.AddData("Data 1");
Thread.Sleep(10000);
e.AddData("Data 2");
OnGotDataCompleted(e);
}
public void SetAsBackground()
{
thread.IsBackground = true;
}
}
public class GotDataCompletedEventArgs : EventArgs
{
private List<string> data;
public GotDataCompletedEventArgs()
{
data = new List<string>();
}
public ReadOnlyCollection<string> Data
{
get { return data.AsReadOnly(); }
}
internal void AddData(string item)
{
data.Add(item);
}
}
}
DaveIf this helped, please vote & accept answer!
Binging is like googling, it just feels dirtier. (Pete O'Hanlon)
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
|
|
|
|
|