Hi,

My complete scenario is a bit to complicated to explain, but my problem is
this:

I start a child-thread from the main thread, the child is not a
BackgroundWorker, but a Thread, since it needs to be in ApartmentState.STA
because it needs to create a Form and display some stuff. It all works
pretty well, unless an exception occurs in the child thread. The application
shows the standard error dialog to the user, and I don't want that. If an
exception occurs, I want to get (at least) the exception message over to the
main thread, and display it there. To accomplish this, I catch the excepion
in the child thread, and does an Invoke call with the string message, to the
main thread. The problem is, it does not switch to the main thread, it's
always the child (using the threads-window in VS to see that)! Very strange,
what might I be doing wrong?

regards

Carl

Re: Invoke does not change thread!? by Jon

Jon
Wed Mar 12 08:05:19 CDT 2008

Carl <ask@4it.com> wrote:
> My complete scenario is a bit to complicated to explain, but my problem is
> this:
>
> I start a child-thread from the main thread, the child is not a
> BackgroundWorker, but a Thread, since it needs to be in ApartmentState.STA
> because it needs to create a Form and display some stuff. It all works
> pretty well, unless an exception occurs in the child thread. The application
> shows the standard error dialog to the user, and I don't want that. If an
> exception occurs, I want to get (at least) the exception message over to the
> main thread, and display it there. To accomplish this, I catch the excepion
> in the child thread, and does an Invoke call with the string message, to the
> main thread. The problem is, it does not switch to the main thread, it's
> always the child (using the threads-window in VS to see that)! Very strange,
> what might I be doing wrong?

At a complete guess, you're calling Invoke on the delegate rather than
on the control. However, a complete example would be better. Could you
post one?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.


--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk

Re: Invoke does not change thread!? by Carl

Carl
Wed Mar 12 10:06:44 CDT 2008

"Jon Skeet [C# MVP]" <skeet@pobox.com> wrote in message
news:MPG.2241e56285b201c8adf@msnews.microsoft.com...
>
> At a complete guess, you're calling Invoke on the delegate rather than
> on the control. However, a complete example would be better. Could you
> post one?
>
> See http://www.pobox.com/~skeet/csharp/complete.html for details of
> what I mean by that.
>

Hi again,

I'm unable to create such short example, but I've tried to copy/paste the
parts of where the threading problem occurs. There are only three classes
involved, I've added comments to describe what I've tried to do.

////////////////////////////////////////////////////////////////////////
// Code in the main thread
// This code start the entire processing of the reports. This is a Form
class instance.
// The exception message should end up here, so it can be shown.
////////////////////////////////////////////////////////////////////////

private void btnPrint_Click(object sender, EventArgs e)
{
ReportsGateway.CreateReport(userLaen, arnIDs, ardIDs, true, true, this);
}

// I've tried having this callback method in the child thread, but did not
work.
// I would actually rather have it in the child thread, because others might
start
// the report processing in the future.
public void ShowThreadError(string text)
{
throw new BofException("Error generating reports." + Environment.NewLine
+ Environment.NewLine + text);
}


////////////////////////////////////////////////////////////////////////
// Code in the report helper (gateway), an in-between layer.
// This is where the child thread is created. This is a static class,
// inheriting from Object
////////////////////////////////////////////////////////////////////////

public static void CreateReport(int userLaen, int[] arnIDs, int[] ardIDs,
bool onlyPreview, bool printCase, MyBaseForm callingForm)
{
CreateMultiReport(userLaen, arnIDs, ardIDs, callingForm);
}


private static void CreateMultiReport(int userLaen, int[] arnIDs, int[]
ardIDs, MyBaseForm callingForm)
{
object[] agOb = new object[4];

agOb[0] = userLaen;
agOb[1] = arnIDs;
agOb[2] = ardIDs;
agOb[3] = callingForm;

Thread t = new Thread(ReportGenerationForm.StartProcessingReports);
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;

ReportGenerationForm.paramObjects = agOb;

t.Start();
}



////////////////////////////////////////////////////////////////////////
// Code in the child thread
// An exception might occur here, that should be pushed up to the main
// UI thread, for nomal display. This is a Form class instance.
////////////////////////////////////////////////////////////////////////

// I've tried to declare this in the main form gui, but no difference.
public delegate void ShowErrorMessage(string text);

public static void StartProcessingReports()
{
ReportGenerationForm frm = new
ReportGenerationForm((int)paramObjects[0],
paramObjects[1] as int[], paramObjects[2] as int[], paramObjects[3]
as MyBaseForm, true, true, true);
IntPtr handle = frm.Handle;
frm.ShowPrint();
}

private void ShowPrint()
{
try
{
ShowDialog();
TopMost = true;
BringToFront();
}
catch (Exception ex)
{
// I've tried lots of different things here, for example calling
Invoke on
// the delegate, calling invoke on the calling form etc etc, but no
use.
ShowErrorMessage d = new ShowErrorMessage((_callingForm as
XXX.YYY.ReportMainthreadForm).ShowThreadError);
IntPtr handle = this.Handle;
this.Invoke(d, new string[] { ex.Message });
}
}

regards

Carl


Re: Invoke does not change thread!? by Jon

Jon
Wed Mar 12 10:28:51 CDT 2008

Carl <ask@4it.com> wrote:
> I'm unable to create such short example

Why? What happened when you tried?

> but I've tried to copy/paste the
> parts of where the threading problem occurs. There are only three classes
> involved, I've added comments to describe what I've tried to do.

Well, one thing I notice is that you're calling ShowDialog(),
TopMost=true, and BringToFront() from within the worker thread.

You shouldn't show a UI from a different thread than the one which
created it. That could well be part of the problem. The usual idea with
worker threads is that *non*-UI work is done in the worker thread, and
all UI updates etc are passed by to the UI thread.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk

Re: Invoke does not change thread!? by Jon

Jon
Thu Mar 13 04:28:20 CDT 2008

Carl <ask@4it.com> wrote:
> I'm aware of that GUI should not be messed with from child threads, but this
> is old legacy code, and it is far too much to refactor, so I have to find a
> workaround.

Well you shouldn't find it surprising that threads aren't behaving
quite as you'd like if you already *know* you're performing invalid
operations on them.

> It all works pretty well, except that I can't get the error
> messages back to the main thread. I tried again to create a short example,
> and I think I got something good enough, which will demonstrate the problem
> (using the thread debug window in VS). I'll attach it to this post.
>
> It consists of three classes, one to start things (the main thread gui
> class), a helper for the report generation, the acual report generator. I've
> added an exception to the report generator.

Okay, there are two forms and you've effectively got two UI threads
running. Your ThreadRunnerForm only ever "sees" the second UI thread,
so when you call Invoke on it it's staying on that second UI thread. If
you want to get back to the UI thread running ThreadStarterForm, you'll
need to call Invoke on the relevant ThreadStarterForm (or some other
control in the same UI thread). If you make your code run
_callingForm.Invoke instead of this.Invoke, it will call back on the
right thread - but that doesn't mean everything will be working as you
really want it to.

I would still recommend trying to avoid using two different UI threads
though. It may well be painful to fix, but I suspect it'll be worth the
effort. Debugging threading issues caused by inappropriate use of UI
threads can be nightmarish.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk

Re: Invoke does not change thread!? by Carl

Carl
Thu Mar 13 05:14:36 CDT 2008

>
> Well you shouldn't find it surprising that threads aren't behaving
> quite as you'd like if you already *know* you're performing invalid
> operations on them.
>
> Okay, there are two forms and you've effectively got two UI threads
> running. Your ThreadRunnerForm only ever "sees" the second UI thread,
> so when you call Invoke on it it's staying on that second UI thread. If
> you want to get back to the UI thread running ThreadStarterForm, you'll
> need to call Invoke on the relevant ThreadStarterForm (or some other
> control in the same UI thread). If you make your code run
> _callingForm.Invoke instead of this.Invoke, it will call back on the
> right thread - but that doesn't mean everything will be working as you
> really want it to.
>
> I would still recommend trying to avoid using two different UI threads
> though. It may well be painful to fix, but I suspect it'll be worth the
> effort. Debugging threading issues caused by inappropriate use of UI
> threads can be nightmarish.
>
> --
> Jon Skeet - <skeet@pobox.com>

Well, I acually thought that it was OK to do it like this, if I did not mess
with the GUI between the threads directly, which I'm not.

The solution you described worked, thanks! I thought that it was 100% OK
thread-wise now, but perhapse it's not?

I think that maybe I should rewrite it even if it's a lot of work, to get it
right, but there is a questions I really must know in that case: When does
it count as a (forbidden) GUI operation? Is it OK to create a Form instance,
which is never showed? Is that GUI-code or not? The quesion might seem
strange, the reason is that I have a ReportViewer on a Form, so I might be
able to create the form to get the report, and send that to the printer,
without showing it.

Thanks again

Carl


Re: Invoke does not change thread!? by Jon

Jon
Thu Mar 13 05:21:32 CDT 2008

Carl <ask@4it.com> wrote:
> Well, I acually thought that it was OK to do it like this, if I did not mess
> with the GUI between the threads directly, which I'm not.
>
> The solution you described worked, thanks! I thought that it was 100% OK
> thread-wise now, but perhapse it's not?

Well, you're never explicitly setting up the message loop on the second
form - and the way the test app works, the message loop won't get much
of a chance to update the progress bar etc, because it'll be busy doing
reporting things. You should really try to keep all your UI operations
on one thread, and your non-UI operations on a different thread.
Currently you've got two UI threads, one of which rarely gets a chance
to actually do UI things.

> I think that maybe I should rewrite it even if it's a lot of work, to get it
> right, but there is a questions I really must know in that case: When does
> it count as a (forbidden) GUI operation? Is it OK to create a Form instance,
> which is never showed? Is that GUI-code or not?

That's okay, but a generally bad idea - it makes for confusing code.

> The quesion might seem
> strange, the reason is that I have a ReportViewer on a Form, so I might be
> able to create the form to get the report, and send that to the printer,
> without showing it.

I'd certainly extract the reporting aspects from the UI aspects, so you
don't need to create a form at all if you don't want to show one. That
much shouldn't be too hard.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk

Re: Invoke does not change thread!? by Carl

Carl
Thu Mar 13 06:13:53 CDT 2008

>> Well, I acually thought that it was OK to do it like this, if I did not
>> mess
>> with the GUI between the threads directly, which I'm not.
>>
>> The solution you described worked, thanks! I thought that it was 100% OK
>> thread-wise now, but perhapse it's not?
>
> Well, you're never explicitly setting up the message loop on the second
> form - and the way the test app works, the message loop won't get much
> of a chance to update the progress bar etc, because it'll be busy doing
> reporting things. You should really try to keep all your UI operations
> on one thread, and your non-UI operations on a different thread.
> Currently you've got two UI threads, one of which rarely gets a chance
> to actually do UI things.
>
>> I think that maybe I should rewrite it even if it's a lot of work, to get
>> it
>> right, but there is a questions I really must know in that case: When
>> does
>> it count as a (forbidden) GUI operation? Is it OK to create a Form
>> instance,
>> which is never showed? Is that GUI-code or not?
>
> That's okay, but a generally bad idea - it makes for confusing code.
>
>> The quesion might seem
>> strange, the reason is that I have a ReportViewer on a Form, so I might
>> be
>> able to create the form to get the report, and send that to the printer,
>> without showing it.
>
> I'd certainly extract the reporting aspects from the UI aspects, so you
> don't need to create a form at all if you don't want to show one. That
> much shouldn't be too hard.
>
> --
> Jon Skeet - <skeet@pobox.com>

Thanks a lot for your help, I think I will extract the report stuff, like
you suggest.

regards

Carl


Re: Invoke does not change thread!? by Jon

Jon
Thu Mar 13 06:18:27 CDT 2008

Carl <ask@4it.com> wrote:

<snip>

> Thanks a lot for your help, I think I will extract the report stuff, like
> you suggest.

Goodo - let me know if you run into any more problems.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk