-2

I'm currently building an app that relies on DataGridView to receive updates via WAMP protocol, which involves working with new message events. My event handler looks like this:

 private async void NewMessage(object sender, MessageEventArgs e)
 {
      await Task.Factory.StartNew(() => DataHolder.TableSource.Add(new 
      CustomData(e.Name, e.Surname, 
      e.Whatever, e.WhoTheHellCares)));
 }

When such an event occurs, an exception is thrown: System.InvalidOperationException "An attempt to access control element which was created in a different thread".

DataHolder is a static class that exists within same namespace with the form class that has this event handler, DataHolder.TableSource is a BindingList<T> which is bound in Form.Load event to the DataGridView control created in Form1.Designer.cs .

I've read an answer to a related issue here, that mentioned await being able to automatically marshall something to UI thread if needed, but my wild and incompetent guess is that await does not recognize a databinding, so it must be told explicitly to do so, how though?

I need a .net 4.5 solution here or proof that tasks and awaits are unable to solve my problem. But I think they are more than able. Just that I have trouble to apply it to my own situation here.

UPDATE

Wow, what the hell.. Even when my handler looks like this, it still gives me the same exception.

 private void NewMessage(object sender, MessageEventArgs e)
 {
      DataHolder.TableSource.Add(new 
      CustomData(e.Name, e.Surname, 
      e.Whatever, e.WhoTheHellCares));
 }

I guess it has something to do with the class itself that is firing an event. Well.. I tried D:

UPDATE I've used debugging tools and here's the deal - the event itself is already nested in another thread. Will try to rewrite the source code for the library I'm using so that it will support progress reports. Wish me f`kin luck D:

Cowbless
  • 93
  • 12
  • If you want to access UI thread from another thread, you have to use Dispatcher or some Windows Forms analog. https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c – Vladimir Arustamian Jul 12 '17 at 11:45
  • *await being able to automatically marshall something to UI* .. You missed the point you read I guess.. That's for the code that comes after the `await` (in case you didn't use `ConfigureAwait(false)`) – Zein Makki Jul 12 '17 at 11:46
  • Where are you updating the data-source ? We can't see that. Why don't you get the data asynchronously and await that. After that, you update your data-source from the result ? – Zein Makki Jul 12 '17 at 11:47
  • https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c/18033198#18033198 Suggests that all I need is some awaits and tasks. Here's my source. [link](https://stackoverflow.com/a/37638258/6168831) I don't think I misread anything. – Cowbless Jul 12 '17 at 11:50
  • @TorlanDelta You can see in the answer found in the [link](https://stackoverflow.com/a/18033198/3185569) you mentioned, that the UI update was done **after** the `await` and not inside the task. – Zein Makki Jul 12 '17 at 11:51
  • user3185569 I don't need to, I've set a `DataSource` property of my `DataGridView` to a `BindingList TableSource`, which makes it update automatically, as I've told you in my post. – Cowbless Jul 12 '17 at 11:53
  • 1
    Possible duplicate of [Access control element from static thread function](https://stackoverflow.com/questions/16012354/access-control-element-from-static-thread-function) – mjwills Jul 12 '17 at 11:53
  • @mjwills I don't think that invoking anything is a good idea. It's so outdated man. I'd love to see some .net 4.5 solution, unless people explain to me why contemporary c# async programming can't handle what I'm trying to do. – Cowbless Jul 12 '17 at 11:56
  • 1
    @TorlanDelta By the way, [This way](https://stackoverflow.com/a/37638258/3185569) you referenced is broken. He didn't even ran anything on a different thread ! There is no `Task.Run` or `Task.Factory.StartNew`. So there is no **Other Thread**, That's why he didn't get that error in his case. [This](https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c/18033198#18033198) is the .NET 4.5 Solution. – Zein Makki Jul 12 '17 at 11:59
  • @user3185569 I see that now. And I've read this post. I just don't know how to apply it to my problem here. How do I set up progress etc. This is why I created this question in the first place. – Cowbless Jul 12 '17 at 12:04
  • @mjwills I choose up-to-date and working. Hence, this question. – Cowbless Jul 12 '17 at 12:04

2 Answers2

1

You are misunderstanding async/await.

Your method will be divided in 2 parts: the before await and the after await. So when code runs it will execute like this:

  1. before await
  2. AWAIT IN ASYNC THREAD
  3. execute your awaited method (what is inside Task.StartNew) in another thread(not necessarily)
  4. When step 3 is complete it will wakeup (marshal) the execution of step 2
  5. run after await

So what is happening is that what is being marshaled to UI thread is the execution of the second part of your method not what is inside the task.

There is more to it than this, but I think this is the steps I used to understand it.

To solve this remove async completely as you are not doing a long process:

private void NewMessage(object sender, MessageEventArgs e)
{
     DataHolder.TableSource.Add(new CustomData(e.Name, e.Surname, 
     e.Whatever, e.WhoTheHellCares));
}

Or if you want to be async and you are in a WinForms control use BeginInvoke

private void NewMessage(object sender, MessageEventArgs e)
{
    this.BeginInvoke((Action)(() => DataHolder.TableSource.Add(new 
      CustomData(e.Name, e.Surname, 
      e.Whatever, e.WhoTheHellCares))));
}
Marcelo de Aguiar
  • 1,432
  • 12
  • 31
  • Thanks, I do need some more down to earth async programming explaination, but the kind of problem I have now is answered by this [post](https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c/18033198#18033198) i just don't know how to implement to my own problem. – Cowbless Jul 12 '17 at 12:13
  • Remove the async from your method signature. Remove the await from the method and dont use a Task. There is no need to do it here as you are not running a long process, just update the data source. – Marcelo de Aguiar Jul 12 '17 at 12:18
  • Check the updated post xD Oops.. And I've been using this simple example as a starting point, because yes, this is not a long running task, but my app will definitely have another datagridview or a chart that will involve some hard calculations. – Cowbless Jul 12 '17 at 12:23
  • Remove the async from the method signature. – Marcelo de Aguiar Jul 12 '17 at 12:26
  • "Cannot convert lambda expression to a delegate type, as it is not a type of delegate" is what compiler says to your suggestion D: – Cowbless Jul 12 '17 at 12:31
  • Sorry Im on mobile. You need to cast the lamda to Action delegate or create a new on Eg.: new Action(() => ...). edited. – Marcelo de Aguiar Jul 12 '17 at 12:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149020/discussion-between-marcelo-de-aguiar-and-torlan-delta). – Marcelo de Aguiar Jul 12 '17 at 14:45
1

An attempt to access control element which was created in a different thread

The issue is that your code is touching a UI element (or something that it relies on) from something other than the main UI thread. You must not do this.

I know that you don't want to use Invoke but it really is the way to solve this problem. These links (and hundreds like it) discuss it:

Based on your comment, another approach you could consider is Looking for .NET 4.5 Progress<T> source code . Have a look at how it uses SynchronizationContext .

mjwills
  • 23,389
  • 6
  • 40
  • 63
  • [This](https://stackoverflow.com/questions/661561/how-to-update-the-gui-from-another-thread-in-c/18033198#18033198) is suggesting other approach, I just don't know how to apply it to my problem. And I've honestly browsed TONS of these questions, I'm not happy with the solutions they give due to out of date approaches. – Cowbless Jul 12 '17 at 12:09
  • 1
    See my most recent comment. Ultimately their suggestion is using a `SynchronizationContext`, which (behind their abstraction) will call [Post](https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Threading.SynchronizationContext.Post);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.6.1);k(DevLang-csharp)&rd=true) which is basically the same as calling `Invoke`. See https://stackoverflow.com/questions/21778078/what-are-difference-between-use-invoke-and-synchronizationcontext-post-object . – mjwills Jul 12 '17 at 12:18