views:

339

answers:

2

So far I am using an FTP object inside a Windows form. FTP object runs in a separate thread, so to ensure that my app doesn't freeze up, I use the following piece of code:

private void OnResponse(string response)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new StringDelegate(OnResponse), new object[] { response });
            return;
        }
    } //end of OnResponse

I am not completely clear on what a string delegate is, but this works.

However, I am now refactoring and wish to hide the ftp into a class. My question is how do I make sure the main thread doesn't freeze? All the references online regarding raising events inside classes make sense, but I haven't found a single example where the application is multithreaded. My biggest concern would be InvokeRequired.

In the code above this is a form. If I hide the ftp object inside a class such as the following:

abstract class MyClass
{
    //data members
    private FTP _ftp;

    //other data members, methods, and properties etc
}

"This" becomes an object of MyClass. I am not sure if InvokeRequired property is implemented on class (perhaps I should make it implement a special interface that has that property?). Or perhaps I am missing something and I am not supposed to use multithreaded objects inside classes?

A: 

The most likely way to design this is to let the caller of 'MyClass' Invoke or not Invoke as needed. I would design your class to fire an event when the Response happens.

Remember that it's only code that interacts with the windows UI that needs to be Invoke-d onto the main thread. Any other handling in OnResponse can be done in the background thread no problem.

If your 'MyClass' isn't a windows object like a form or control, then you don't have the InvokeRequired just like you said. But it doesn't NEED InvokeRequired. If some other class representing a UI object does need to do something in response to the FTP object, well that UI object can do the InvokeRequired test.

I hope that's clear!


EDIT: addl info

The OnReceived handler would definitely be in your FTP class. So I'm thinking of something like this:

public class MyFTPClass { public event EventHandler DataReceived; // the UI form can subscribe to this event

 private void OnReceived()
 {
   //so the FTP has returned data
   // Do stuff here that can be on the background thread like saving
   // the file in an appropriate place, or whatever.


   // now trigger the DataReceived event.  Anyone who cares about data being received
   // can subscribe to this and do whatever is appropriate.
   if (DataReceived) DataReceived(this, EventArgs.Empty);

}

This is a rough outline. But the basic idea is that you do what you can locally in the background thread. Then you can notify the UI through an event.

Clyde
Thanks Clyde! it's somewhat clear. So say I have MyClass defined as in the second piece of code - ftp is its data member. Where would OnResponse() belong? i am guess it's going to live in the UI form. UI forms calls MyClass object (thas has ftp). You say I should have event fire inside my class when OnResponse() happens...But how would I know when it happens if OnResponse lives in the UI form...I am confused. Could you provide code (or pseudocode) to outline what you are thinking?
gnomixa
In your case UI subscribes to DataReceived event, and can trigger another event once DataReceived fires....Inside that event (on UI), InvokeRequired will be checked. Is that the idea?
gnomixa
+1  A: 

You need a Control or something derived from Control (doesn't have to be a Form) that was created on the UI thread. Your MyClass likely shouldn't be updating the UI directly, so it's not really relevant here - MyClass would probably raise an event or invoke a callback.

Where it gets important is up at the UI when you want to change something on a Form based on an event coming from the FTP library. For that you need a Control or anything derived from Control (again, doesn't have to be a Form) that was created on the UI thread. Use that control to check InvokeRequired, and if it's true, call Invoke. The original uses a custom delegate (probably comes from the FTP sample, as it looks really familiar to me), but you could use any delegate you want.

There are plenty of examples on the web for using Control.Invoke, so you should be able to get it implemented fairly easily.

ctacke
I think I got it after looking once again at the code. My OnResponse() will live inside MyClass. Inside OnResponse I will raise an event that UI Control will subscribe to. Once this event fires off, UI will check InvokeRequired(as it does now)
gnomixa
Also, if you have a chance, could you please explain what this particular line does: this.Invoke(new StringDelegate(OnResponse), new object[] { response }); My confusion comes from the fact that StringDelegate doesn't actually DO anything, so why is it there? I searched a lot online but none of the explanations made sense. Thanks in advance!
gnomixa
That delegate calls back to the function itself, so the second pass through InvokeRequired will be false and it will execut the code below the if block. This allows you to have one method that handes the event whether it's on the UI thread or a worker thread. To say it doesn't do anything is incorrect - it certainly does. It marshals the thread context back to the UI thread if it's not already there, which is required to affect UI elements.
ctacke
I see. I guess my confusion came from the fact that in my case there is nothing below the IF block. In which case it is redundant. The original example I followed had something, but I didn't need to update UI.
gnomixa
I have implemented the suggested solution successfully so far in my test. One thing to note that's very IMPORTANT: when you subscribe to events of the custom class inside your Control, please remember that the method that gets invoked after the custom class event is raised is RUNNING IN THE BACKGROUND THREAD, NOT UI THREAD - therefore any UI updates will have to be done with Invoke. Seems obvious now, but didn't about 2 hours ago. Thanks to Ilya Tumanov whose numerous posts on MSDN resolved this issue for me.
gnomixa
Sure - that's the whole point of the way the OnResponse handler you quoted is written. Without the Invoke, you'll get an exception that tells you to call Invoke.
ctacke
If only. That exception didn't tell me anything, just said general exception w/o the error message. I googled and came upon Ilya's answers and it all made sense. Overall I would say this approach is more work, but more dynamic in the long run.
gnomixa