Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronizing worker with UI thread

Working on an existing project, I've got to use WinForms (haven't worked with it for a while) and have got an issue synchronizing with the UI thread.

The design I've got to integrate with works as follows: A BackgroundWorker gets an Action as a parameter and executes it asynchronously. The action I'm working on has two parts; a core class (containing the business logic) and a GUI part being notified by the core through events if it has to request user interaction.

I've added the handle creation to the constructor of the form

if (!IsHandleCreated)
{
    //be sure to create the handle in the constructor
    //to allow synchronization with th GUI thread
    //when using Show() or ShowDialog()
    CreateHandle();
}

With this, following code works:

private DialogResult ShowDialog(Form form)
{
    DialogResult dialogResult = DialogResult.None;
    Action action = delegate { dialogResult = form.ShowDialog(); };
    form.Invoke(action);
    return dialogResult;
}

For this example, the startup location has been set to windows default.

If I change it to:

Action action = delegate { dialogResult = form.ShowDialog(ParentWindow); };

Where ParentWindow is an instance of IWin32Window and the WindowStartupLocation is set to CenterParent. I get a cross-thread exception when calling form.Invoke(action).

Cross-thread operation not valid: Control 'ActivationConfirmationForm' accessed from a thread other than the thread it was created on.

Questions:

  • Why is there a cross thread exception only when setting the startup location as CenterParent? And how do I avoid it?
  • Why is form.InvokeRequired always false?

Both are probably related!?

[edit] @Reniuz: You're not missing anything here ;) The call is being made from a listener being notified by the core

private static void OnActivationConfirmationRequired(DmsPackageConfiguratorCore sender,
ConfigurationActivationConfirmationEventArgs args)
{
    args.DoAbort = (ShowDialog(new ActivationConfirmationForm(args.Data)) == DialogResult.No);
}

Everything at my disposal is in the GUI interface

/// <summary>
/// Interface defining methods and properties used to show dialogs while performing package specific operations
/// </summary>
public interface IPackageConfiguratorGui
{
/// <summary>
/// Gets or sets the package configurator core.
/// </summary>
/// <value>The package configurator core.</value>
IPackageConfiguratorCore PackageConfiguratorCore { get; set; }

/// <summary>
/// Gets or sets the parent window.
/// </summary>
/// <value>The parent window.</value>
IWin32Window ParentWindow { get; set; }

/// <summary>
/// Gets the package identifier.
/// </summary>
/// <value>The package identifier.</value>
PackageIdentifier PackageIdentifier { get; }
}
like image 946
Philippe Avatar asked Jan 26 '12 12:01

Philippe


People also ask

What does it mean to synchronize threads?

Synchronization is the cooperative act of two or more threads that ensures that each thread reaches a known point of operation in relationship to other threads before continuing. Attempting to share resources without correctly using synchronization is the most common cause of damage to application data.

What is UI thread and worker thread?

People use the word "worker" when they mean a thread that does not own or interact with UI. Threads that do handle UI are called "UI" threads. Usually, your main (primary) thread will be the thread that owns and manages UI. And then you start one or more worker threads that do specific tasks.

What is the need of synchronizing threads?

Thread synchronization is the concurrent execution of two or more threads that share critical resources. Threads should be synchronized to avoid critical resource use conflicts. Otherwise, conflicts may arise when parallel-running threads attempt to modify a common variable at the same time.

What is synchronization in reference to a thread C#?

Explanation: When two or more threads need to access the same shared resource, they need some way to ensure that the resource will be used by only one thread at a time, the process by which this is achieved is called synchronization.


3 Answers

Seeing form.InvokeRequired at false is the core of your problem. You know it has to be true. The simple explanation is that the form object that is passed to your ShowDialog() method is the wrong object. Classic mistake is to use new to create the instance instead of using the existing instance of the form object, the one that the user is looking at and was created on the main thread. Be sure that the threaded code has a reference to that form object so it can pass the correct reference. Only use Application.OpenForms[0] if you can't get it right.

In general, decouple the threaded code from the user interface. A worker thread has no business displaying a dialog. You can make it work but it doesn't work well in practice. The dialog pops up without the user expecting it. Making accidents likely, the user might click or press a key a fraction of a second before the dialog pops up. Dismissing the dialog without even seeing it. The CreateHandle() hack similarly should not be in your code. Just don't start the thread until the user interface is ready. Signaled by the form's Load event.

like image 153
Hans Passant Avatar answered Sep 27 '22 18:09

Hans Passant


ok I have no permission to "comment" since i'm a new user, so i'll just use this answer space.

you are creating a new instance of a form ActivationConfirmationForm, whichever thread you are in, the creation of this form and the execution of ShowDialog is executing in the same thread context, since this is true, the InvokeRequired (see msdn) is obviously going to be false since the form that you want to access is created in the thread in which you are accessing it. No need to use invoke/begininvoke etc. Kind of what @reniuz was worried about.

like image 42
user734028 Avatar answered Sep 27 '22 17:09

user734028


The form belongs to a different thread to the parent.

It appears that WinForms CenterParent position argument makes a call to the WinForms .Net object rather than using the Win32 API to find the parent window position from the HWND, and this cross-thread call is what causes the cross thread exception.

The real answer is worker threads shouldn't have UI. They should give a result which indicates that user intervention is required and the main thread should take care of user interaction.

Failing that, don't set a parent window for the worker thread GUI. It might (just) be workable if you have only one worker thread, but it will cause all sorts of confusion if you have more than none.

If you absolutely must, use P/Invoke to find the current window position of the parent window from the Win32API and set it explicitly.

like image 42
Ben Avatar answered Sep 27 '22 16:09

Ben