0

I have timer which is calling list of stored actions. I want those actions to be called asynchronously. So I wrapped my CPU bound operation into task, then made async/await in action. However, it's not updating combo box. Clearly the context is not switching back to UI, but I don't understand why and what I should do to fix it.

Code in main form:

public FormMain() { InitializeComponent(); pt = new PeriodicTask(() => Execute()); pt.Start(); actions = new List<ActionWrapper>(); actions.Add(new ActionWrapper() { Periodic = false, MyAction = async () => { bool b = await NetworkOperation(); comboBoxPairs.DataSource = pairs; // this doesn't update combo box comboBoxPairs.DisplayMember = "Name"; //comboBoxPairs.Refresh(); // this is even crashing app }}); } private Task<bool> NetworkOperation() { return Task.Run(() => { // CPU bound activity goes here return true; }); } private void Execute() { Parallel.ForEach(actions, new ParallelOptions { MaxDegreeOfParallelism = 10 }, x => { x.MyAction(); if (!x.Periodic) actions.Remove(x); }); } 

Timer class:

public class PeriodicTask { private System.Threading.Timer timer; private int dueTime; private int periodTime; private Action callBack; public PeriodicTask(Action cb) { callBack = cb; timer = new System.Threading.Timer(Task, null, Timeout.Infinite, Timeout.Infinite); dueTime = 100; periodTime = 5000; } public void Start() { timer.Change(dueTime, periodTime); } public void Stop() { timer.Change(Timeout.Infinite, Timeout.Infinite); } private void Task(object parameter) { callBack(); } } 

This is the wrapper class I use to hold action:

public class ActionWrapper { public bool Periodic { get; set; } public Func<Task> MyAction { get; set; } } 
9
  • 1
    The problem is that action will be run on an arbitrary thread pool thread, you are not triggering the async action from the WinForms synchronisation context. The await will work in that the continuation will return to the same synchronisation context, but that is not the WinForms one because Parallel.ForEach just queues up tasks with the default scheduler - the pool. You also have a race condition in that if the network call finishes first, there won't be any actions lined up because you haven't made them yet. Slim chance, granted, but still looks odd. Commented May 31, 2016 at 7:30
  • Excuse me, ignore the bit about the race condition. I think I'm mistaken, was misreading a bit of the code. Commented May 31, 2016 at 7:36
  • shall I pass TaskScheduler.FromCurrentSynchronizationContext() to action? But then I can't use async/await and will have to change context myself with TPL... Commented May 31, 2016 at 7:38
  • So what is the pattern when the triggering context is not defined, but you still need interact with UI thread once the task is finished? Commented May 31, 2016 at 7:43
  • What is the value of the pairs field/property? When and where is it set? Commented May 31, 2016 at 7:51

1 Answer 1

1

It's not that it does not switch back, it does not start on a UI thread in the first place because you are using System.Threading.Timer, which processes the ticks on thread pool threads, out of WPF context.

You can replace it with DispatcherTimer.

If a System.Timers.Timer is used in a WPF application, it is worth noting that the System.Timers.Timer runs on a different thread then the user interface (UI) thread. In order to access objects on the user interface (UI) thread, it is necessary to post the operation onto the Dispatcher of the user interface (UI) thread using Invoke or BeginInvoke. Reasons for using a DispatcherTimer opposed to a System.Timers.Timer are that the DispatcherTimer runs on the same thread as the Dispatcher and a DispatcherPriority can be set on the DispatcherTimer.

Also, you need to deal with re-entrance, same as for System.Threading.Timer, because a Cpu bound operation could still be processing the previous tick.

using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace WpfApplication1 { public partial class MainWindow : Window { DispatcherTimer timer = new DispatcherTimer(); long currentlyRunningTasksCount; public MainWindow() { InitializeComponent(); Loaded += MainWindow_Loaded; timer.Interval = TimeSpan.FromSeconds(1); timer.Tick += async (s, e) => { // Prevent re-entrance. // Skip the current tick if a previous one is already in processing. if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0) { return; } try { await ProcessTasks(); } finally { Interlocked.Decrement(ref currentlyRunningTasksCount); } }; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { // This one would crash, ItemsSource requires to be invoked from the UI thread. // ThreadPool.QueueUserWorkItem(o => { listView.Items.Add("started"); }); listView.Items.Add("started"); timer.Start(); } async Task ProcessTasks() { var computed = await Task.Run(() => CpuBoundComputation()); listView.Items.Add(string.Format("tick processed on {0} threads", computed.ToString())); } /// <summary> /// Computes Cpu-bound tasks. From here and downstream, don't try to interact with the UI. /// </summary> /// <returns>Returns the degree of parallelism achieved.</returns> int CpuBoundComputation() { long concurrentWorkers = 0; return Enumerable.Range(0, 1000) .AsParallel() .WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount - 1)) .Select(i => { var cur = Interlocked.Increment(ref concurrentWorkers); SimulateExpensiveOne(); Interlocked.Decrement(ref concurrentWorkers); return (int)cur; }) .Max(); } /// <summary> /// Simulate expensive computation. /// </summary> void SimulateExpensiveOne() { // Prevent from optimizing out the unneeded result with GC.KeepAlive(). GC.KeepAlive(Enumerable.Range(0, 1000000).Select(i => (long)i).Sum()); } } } 

If you need a precise control on what's happening, you are better off with queueing events and displaying them independently of processing:

using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; namespace WpfApplication2 { public partial class MainWindow : Window { DispatcherTimer fastTimer = new DispatcherTimer(); BackgroundProcessing processing = new BackgroundProcessing(); public MainWindow() { InitializeComponent(); processing.Start(); fastTimer.Interval = TimeSpan.FromMilliseconds(10); fastTimer.Tick += Timer_Tick; fastTimer.Start(); } private void Timer_Tick(object sender, EventArgs e) { Notification notification; while ((notification = processing.TryDequeue()) != null) { listView.Items.Add(new { notification.What, notification.HappenedAt, notification.AttributedToATickOf }); } } protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); processing.Stop(); } } public class Notification { public string What { get; private set; } public DateTime AttributedToATickOf { get; private set; } public DateTime HappenedAt { get; private set; } public Notification(string what, DateTime happenedAt, DateTime attributedToATickOf) { What = what; HappenedAt = happenedAt; AttributedToATickOf = attributedToATickOf; } } public class BackgroundProcessing { /// <summary> /// Different kind of timer, <see cref="System.Threading.Timer"/> /// </summary> Timer preciseTimer; ConcurrentQueue<Notification> notifications = new ConcurrentQueue<Notification>(); public Notification TryDequeue() { Notification token; notifications.TryDequeue(out token); return token; } public void Start() { preciseTimer = new Timer(o => { var attributedToATickOf = DateTime.Now; var r = new Random(); Parallel.ForEach(Enumerable.Range(0, 2), i => { Thread.Sleep(r.Next(10, 5000)); var happenedAt = DateTime.Now; notifications.Enqueue( new Notification("Successfully loaded cpu 100%", happenedAt, attributedToATickOf)); }); }, null, 0, 1000); } public void Stop() { preciseTimer.Change(0, 0); } } } 

UPDATE: For Windows Forms you could replace DispatcherTimer with the System.Windows.Forms.Timer in the second code sample.

Sign up to request clarification or add additional context in comments.

3 Comments

Unfortunately it's not WPF application, but Windows Forms, as I mentioned in tag.
Then you could use just an infinite loop with await Task.Delay(10) started from the Form.Load event and polling on a queue as shown in the second example.
Or, even better, you could replace DispatcherTimer with the System.Windows.Forms.Timer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.