views:

164

answers:

1

I asked a similar question here; thanks to everyone who provided suggestion! However, it seems that my problem is bigger than the one described above, so I am posting a new question.

The issue is that I need to keep my UI responsive when I am loading a large document using a third party control called document. The document is a class embedded in a third party control, there is no way for me to new it, and it doesn't have Invoke function.

I am trying to use Backgroundworker for this. The code I try is of this form:

public class MyForm: Form
{
private delegate bool OpenFile(string filePath);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    OpenFile oF = new OpenFile(document.Open);
    var openFile = this.Invoke(oF, MyFileName);  

    e.Result = openFile;
}
}

Now, in the UI thread, where document belongs, when it is doing the Open operation, a dialog box is supposed to come up and updating itself and the main thread is supposed to remain responsive ( i.e., I can still drag and drop the form anyway I like). This is what I was trying to accomplish.

The problem is that the above code will make both of my threads ( the main UI thread and the dialog box thread) hang during the time of loading. My question is,is there anyway to fix this issue?

P/S: I have tried var openFile = Invoke(oF, MyFileName) and var openFile = BeginInvoke(oF, MyFileName) and var openFile = OtherControl.Invoke(oF, MyFileName), all didn't help.

+2  A: 

Your included example doesn't show quite enough to clearly identify the problem. My assumption is that your class doesn't contain an explicit Invoke() method, so your example is calling Form.Invoke(). If that's the case then you're misunderstanding the purpose of that method.

Form.Invoke() ensures that the included delegate is executed on the UI thread. Therefore your background thread isn't actually doing the work, instead your background thread is returning to your UI thread prior to executing the delegate... hence the unresponsive UI.

You might be the victim of an unfortunate ambiguity... both System.Delegate and System.Windows.Forms.Form & Control contain methods named Invoke() and BeginInvoke() but serve different purposes. Unfortunately this is where your sample is a little incomplete. Is OpenFile a delegate? Does your class contain an explicit Invoke() method?


Update: After some discussion in the comments the feasible options were whittled down to either:

  • Trying to preload the data to a temporary in-memory location on a background thread, then loading the 3rd Party UI control from that location versus from the filesystem/network

  • Using .NET Reflector to examine the internal/non-public API of the control and attempt to use Reflection to override the current behavior by calling non-public methods or setting fields/properties

STW
Yes, you are right, `document` doesn't contain an `Invoke()` method. So how would you fix the problem?
Ngu Soon Hui
`OpenFile` is a my own delegate, where as the class ( `document`) doesn't contain an explicit 'Invoke' ( its not a proper control, just a class)
Ngu Soon Hui
Also, the `Invoke` is an invoke on the form; I've updated the question
Ngu Soon Hui
If oF is a delegate that takes a string, you can just call oF(someString) -- that runs the oF delegate with the argument you pass. (You can also call Delegate.DynamicInvoke, you'd normally only do this if you didn't have a defined delegate type like your OpenFile delegate type.)
itowlson
@itowlson, I understand. But this point is trivial and doesn't help in solving the problem at hand.
Ngu Soon Hui
It's not trivial, it's fundamental to the problem you're seeing. Your UI is blocking because, as Yoooder has explained, `this.Invoke(oF, MyFileName)` is running in the UI thread. (Because `this` is the form instance.) That's what Invoke does: it makes the delegate run on the invokee's thread. So now the UI thread is blocked opening the file, and the BackgroundWorker thread is blocked waiting for Invoke to return. By contrast, `oF(MyFileName)` will run in the BackgroundWorker thread, and will not block the UI. Give it a quick try!
itowlson
like itowlson has repeated... calling `Invoke()` ***is explicitly telling your delegate to execute on the UI thread!***. Replace `Invoke(oF, myFileName)` with `oF(myFileName)` and that should ensure your intensive operation is executed on the background thread.
STW
I'm tipping if he runs oF(myFilename) directly in the background worker thread, he will get the usual cross thread GUI exception. So, @NGu Soon Hui, once again, if at all possible use this.BeginInvoke() (where this is the Form). It is **guaranteed** by the framework that Control.BeginInvoke will not block (wait). Double-check that you are calling BeginInvoke on the Form instance.
Ash
@Ash, unfortunately, I've tried `this.BeginInvoke`, and yet the threads still hang.
Ngu Soon Hui
@Yooder, replacing `Invoke(oF, myFileName)` with `oF(myFileName)` will generate a cross thread exception at the end.
Ngu Soon Hui
@Ngu, if 'this' is a Form instance it cannot block/lock because it is simply calling the WinAPI PostMessage() to place a message on the GUI threads message queue. PostMessage does not care if the GUI thread is locked or not.
Ash
@Ngu if `oF(myFileName)` is generating a cross-thread exception then it is because it is updating the UI which necessitates that it execute on the UI thread. Therefore you have two options: (1) Remove the `BackgroundWorker` implementation--as it buys you absolutely nothing and in fact slows the process down further, or (2) refactor the method being called by `oF()` to seperate out the data-intensive operations from the UI updates--then background the data-load and only return to the UI to update the screen when complete.
STW
@Yooder, as mentioned in my question, the data-intensive operation can only be done by a property inside a control on the main UI thread. So how can I separate that out?
Ngu Soon Hui
I see that you're depending on a third-party control; what options do you have for opening the file? Are you currently loading it across the network? Could you use a background-thread to load the file to a `MemoryStream` and open it from in-memory rather than from disk or from the network? Have you used .NET Reflector to review the behaviors of the control? Provided you can comprehend their control, and that it is moderately well-designed interally, then you may be able use Reflection to access non-public methods/properties and coerce the behavior
STW
@Yooder, maybe that's the only way I can take. In the mean time, can you edit your answer to reflect this knowledge so that I can accept your answer? Also remember to put put a comment to this question after you have done it so that I will be notified.
Ngu Soon Hui
All set........
STW