-1

I am trying to display error message notification using MessageBox.Show() method. But I am getting Cross thread operation issue. I used the below code. How can I resolve the cross thread issue? I tried with MethodInvoker but it is not solving my issue. Kindly suggest me the guidelines to resolve this issue.

 public static class Notification()
 {

   public static void ShowErrorMessage(IWin32Window owner, String msg)
   {
       MessageBox.Show(owner, msg, Caption+ " - " + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
   }

}

Issue :

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

Suryakavitha
  • 1,371
  • 5
  • 33
  • 49
  • Do you know how to use Control.Invoke? (If not, see: https://stackoverflow.com/questions/6650691/invoke-in-windows-forms) – Wyck May 28 '20 at 16:03
  • @Wyck - Invoke is possible in Form page. This is static class . Hence can not able to use This.Invoke here. – Suryakavitha May 28 '20 at 16:07
  • Try this: `public async static Task ShowErrorMessage(IWin32Window owner, string msg) { await Task.FromResult(MessageBox.Show(msg, Caption + " - " + "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)); }` and call it as `await Notification.ShowErrorMessage(this, "Some Message");` – Jimi May 28 '20 at 16:14
  • ... Or `await Task.FromResult(MessageBox.Show(owner, msg, ...);` Note that is kind of *backwards*. Since your method requires an `IWin32Window` instance, the instance need to be an existing one, so you can do the exact opposite and post (in different manners, I'm not suggesting `SynchronizationContext.Post()`) to the `SynchronizationContext` of the Window that invokes the `Notification` class method. Of course you need to call `await Notification.ShowErrorMessage(...)` from an async handler or other method of the calling Form/Window, to let `async/await` capture the sync context. – Jimi May 28 '20 at 16:22
  • Early in your application, when you're on the UI thread, stash the current dispatcher (`Dispatcher.CurrentDispatcher`) some place that the static class can access it (Like a static variable). That will be your mechanism for dispatching calls back to the UI thread. – Wyck May 28 '20 at 16:32
  • Just define `owner` as type `Control` (which implements `IWin32Window` in addition to `ISynchronizeInvoke`) instead of `IWin32Window`. Then you have all the tools necessary to invoke the call to `MessageBox.Show` on the UI thread. What are you supplying for `owner` that would not be a `Control` to start with? – TnTinMn May 29 '20 at 02:40

2 Answers2

1

Here is a full example of calling a MessageBox from another thread. The trick here is to stash the dispatcher somewhere (like a static variable in some other static class -- in this example, I put the static variable in the same class, but you could put it anywhere) And then you ask the Dispatcher to Invoke some call on the main thread. You can do this synchronously (Invoke) or asynchronously (BeginInvoke) to suit your needs.

static System.Windows.Threading.Dispatcher d; // Save the dispatcher in this global

private void Form1_Load(object sender, EventArgs e)
{
    d = System.Windows.Threading.Dispatcher.CurrentDispatcher;
    System.Threading.Timer t = new System.Threading.Timer((obj) => {
        d.Invoke(() => {
            MessageBox.Show("hi!");
        });
    }, null, 1000, System.Threading.Timeout.Infinite);

}
Wyck
  • 10,311
  • 6
  • 39
  • 60
  • You don't need to store a Dispatcher object, you can simply pass `Dispatcher.CurrentDispatcher` to the method (the OP's `ShowErrorMessage()`) instead of the `IWin32Window` object and `.Invoke()` from there. – Jimi May 28 '20 at 17:08
  • @Jimi thatis technically true that you can pass it in -- that's a fair implementation of "stash it somewhere". There's a hidden danger that you cannot call Dispatcher.CurrentDispatcher from the foreign thread so it must be retrieved in one thread and then "passed" to the foreign thread. That implies that it's been stored *somewhere*. A static variable is just the easiest such place to understand. The asker even specifically mentioned a concern that the class was _static_, misunderstanding difference between "this.Invoke" vs "Control.Invoke", where _this_ could have been "passed" too. – Wyck May 28 '20 at 18:44
  • The `Dispatcher` object is created in the Form instance that's calling that method. There's no need to store that object nor there's the need for it to be `static`. You can pass `Dispatcher.CurrentDispatcher` to `static void Whatever(Dispatcher dispatcher, ...)` without any problem. In any case, this is a good method, if one **really** wants to show a `MessageBox` from another Thread and keep it app-modal (maybe a re-design is a better choice, who knows :). – Jimi May 28 '20 at 18:50
  • @Jimi I tried calling Dispatcher.CurrentDispatcher from the main thread and it dispatches to thread 1 (the main UI thread). If you call Dispatcher.CurrentDispatcher from a worker thread, it creates a new dispatcher and dispatches to it will dispatch the call to a different thread (not the ui thread). In my case it had managed ID 4. So I'm not sure what you're trying to say. That means, in my example, if you replace `d.Invoke` with `System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke`. It'll invoke the MessageBox on a worker thread, **not** the UI thread. Then you'd have 2 UI threads. – Wyck May 28 '20 at 21:37
  • No, what I said doesn't apply to your sample code, it applies to the OP code (the code you're trying to fix, right?). There, it's the UI Thread that calls the static method of the `Notification` class. It's not a Lambda and it accepts parameters. You can call that static method without storing a Dispatcher object. Just try it. – Jimi May 28 '20 at 21:54
  • @Jimi, OK, I see. But why's do you think the asker is getting a cross thread error if they're calling ShowErrorMessage from the UI thread? – Wyck May 28 '20 at 22:24
  • Because that static class is probably created/used in another Thread and the OP is trying to call that method from the UI Thread, passing `this` as the `IWin32Window` object to set the MessageBox owner so the dialog won't *float away* and, as a consequence, a `Cross thread operation` exception is raised. Or there's a Form instance passed on somewhere else and something is trying to use the instance as `[Instance] as IWin32Window`. The former scenario seems more probable (the latter may raise the exception before that). Maybe the OP will clarify the actual context, sooner or later. – Jimi May 28 '20 at 22:48
0

It seems there is an answer here MessageBox.show() is it not safe to call in worker thread? , though the question is a bit misleading.

Also, this Microsoft documentation page explains in detail how to make thread-safe calls to WinForm controls, and provides you with code examples.