1

I want to update UI from another thread
I have many choices to execute code in the UI Thread context:

1: using BeginInvoke/EndInvoke methods:
pseud-code

public delegate int AddItem(object Item);
public Form1 F = (Form1)Application.OpenForms["Form1"];
private static async DoSomething()
{
    AddItem ad = new AddItem(F.ls1.Items.Add);
    await Task.Run(() =>F.EndInvoke(F.BeginInvoke(add,"NewItem")));
}


2: using Progress / IProgress : I don't know how to Implement such thing

is there another ways to do that ? and which is the preferred way ?

note: the thread which calls the Task not the same UI Thread so maybe the Progress not works here

Rickless
  • 1,377
  • 3
  • 17
  • 36
  • 1
    I don't think this is duplicating the question you refer to is for WPF – Rickless Jan 12 '15 at 08:03
  • True, but does not change the approach. Just use "normal `Invoke`" instead of `Dispatcher.Invoke`. If you have Problems using/implementing that I am happy to help with the specifics... – Christoph Fink Jan 12 '15 at 08:05
  • 2
    You should not be threading, you cannot multithread the UI. You are using Invoke completely wrong. The correct usage of `BeginXXX` and `EndXXX` is `Task.FromAsync(F.BeginInvoke(add, "NewItem"), F.EndInvoke)`. – Aron Jan 12 '15 at 08:05
  • 1
    @ChrFin No! You can't use Threads with a methods called `OpenForms(string)`. – Aron Jan 12 '15 at 08:06
  • @Aron: I did never say to use threads with `OpenForms`. My answer is to the main question which is `I want to update UI from another thread` and his "sub-question" `is there another ways to do that ? and which is the preferred way ?`! – Christoph Fink Jan 12 '15 at 08:10
  • The "proper" way to execute code in the UI thread context depends on your application. However I expect that you SHOULD NEVER EVER LEAVE THE UI THREAD CONTEXT IN THE FIRST PLACE. There are two acceptable reasons to leave the UI thread context. "CPU bound computation" and "Legacy code". – Aron Jan 12 '15 at 08:10
  • @ChrFin Yes. You did say that you use threads with `OpenForms`. `Task.Run(() =>F.EndInvoke(F.BeginInvoke(add,"NewItem")))`. At least for all implementation of `Task.Run` that I know of. Plus all implementations of `Delegate.BeginInvoke` that I know of. – Aron Jan 12 '15 at 08:11
  • thank you Aron,please forgive my misunderstood but why I "multithread the UI", why I can't use threads with method uses form object from OpendForms property ? – Rickless Jan 12 '15 at 08:16
  • @MadMass its historical. Someone (at Microsoft) at some point decided it was easier if UI elements only get accessed from a single thread. WinForm elements are what we call `Single Threaded Apartment` which means it crashes the application when you try to use them from the wrong thread. However the main point is that with .net 4.5 onwards, the vast majority of programs should be single threaded. – Aron Jan 12 '15 at 08:19
  • @Aron I use multithread not because I just wanted to do this, my application use TcpListener to listen for incoming connection and there must be infinite loop to keep listening and I said "pseud-code" above I just wanted to know how to call method from UI Thread context – Rickless Jan 12 '15 at 08:29
  • @MadMass http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.accepttcpclientasync(v=vs.110).aspx I/O is one of the reasons to NOT use threading. – Aron Jan 12 '15 at 08:31

2 Answers2

5

You're making it overly complicated, IMO:

F.Invoke((MethodInvoker)delegate { F.ls1.Items.Add("NewItem"); });

would work perfectly well. Do you need the async here? I don't think you do, since this is not likely to be an expensive operation: not much benefit releasing the worker thread just for that.

Note: personally I'd prefer it if the F encapsulated more of this directly, note; for example:

F.Invoke((MethodInvoker)delegate { F.AddCategory("NewItem"); });
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I'm sorry this is my fault I did not express my question very well I think I have to delete this question – Rickless Jan 12 '15 at 08:18
  • @MadMass why not just edit? – Marc Gravell Jan 12 '15 at 08:19
  • @MadMass You should read Stephen Cleary's blog on the matter. http://blog.stephencleary.com/2013/10/taskrun-etiquette-and-proper-usage.html In short. The correct way to thread with async, is NOT to thread. – Aron Jan 12 '15 at 08:21
1

I always recommend developers to avoid Invoke/BeginInvoke/RunAsync, because it encourages a poor design: your backend/business logic types end up driving the UI (and taking a dependency on a specific UI framework).

A better approach is to either use IProgress<T>/Progress<T> (if your UI updates are progress updates) or IObservable<T> (if your backend is asynchronously producing a series of values).

Adding items to a list sounds to me more like an asynchronous series, but since you specifically asked about IProgress<T> I'll show an example of that:

private static async Task DoSomethingAsync()
{
  Progress<object> progress = new Progress<object>(item => F.ls1.Items.Add(item));
  await Task.Run(() => BackgroundWork(progress));
}

private static void BackgroundWork(IProgress<object> progress)
{
  // Do background work here.
  var item = "NewItem";

  // Notify UI of progress.
  if (progress != null)
    progress.Report(item);
}

If you wanted to use IObservable<T>, then your code could look something like this:

private static void StartSomething()
{
  IObservable<object> items = BackgroundWork();
  items.ObserveOn(F).Subscribe(item => F.ls1.Items.Add(item), ex => HandleException(ex));
}

Note that with both of these approaches, the code doing the background work does not care what context it executes in. It just produces a series of progress updates or a series of values. The UI layer then consumes those progress reports/values and updates the UI accordingly.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810