2016-05-31 6 views
0

Ich habe Timer, die Liste der gespeicherten Aktionen aufrufen. Ich möchte, dass diese Aktionen asynchron aufgerufen werden. Also habe ich meine CPU-gebundene Operation in Task verpackt und dann in Aktion async/await gemacht. Das Kombinationsfeld wird jedoch nicht aktualisiert. Es ist klar, dass der Kontext nicht zur Benutzeroberfläche zurückkehrt, aber ich verstehe nicht, warum und was ich tun sollte, um es zu beheben.async/Warten Sie nicht auf den UI-Thread zurück

-Code in Hauptform:

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-Klasse:

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(); 
    } 
} 

Dies ist die Wrapper-Klasse-I-Aktion zu halten verwenden:

public class ActionWrapper 
{ 
    public bool Periodic { get; set; } 
    public Func<Task> MyAction { get; set; } 
} 
+1

Das Problem besteht darin, dass die Aktion für einen beliebigen Thread-Thread ausgeführt wird. Sie lösen die asynchrone Aktion aus dem WinForms-Synchronisationskontext nicht aus. Das Warten funktioniert, indem die Fortsetzung zum selben Synchronisationskontext zurückkehrt, aber das ist nicht das WinForms-Format, weil Parallel.ForEach nur Aufgaben mit dem Standard-Scheduler - dem Pool - in die Warteschlange stellt. Sie haben auch eine Race-Bedingung, dass, wenn der Netzwerk-Anruf zuerst beendet wird, keine Aktionen anstehen, weil Sie sie noch nicht gemacht haben. Schlanke Chance, gewährt, aber sieht immer noch seltsam aus. –

+0

Entschuldigen Sie, ignorieren Sie den Teil über die Race Condition. Ich glaube ich irre mich, habe ein bisschen Code falsch verstanden. –

+0

soll ich 'TaskScheduler.FromCurrentSynchronizationContext()' an Aktion übergeben? Aber dann kann ich async/await nicht verwenden und muss den Kontext selbst mit TPL ändern ... – Pablo

Antwort

1

Es ist nicht, dass es nicht Zurück schalten, es startet nicht auf einem UI-Thread an erster Stelle, weil Sie System.Threading.Timer verwenden, die proce Setzt die Ticks auf Threadpool-Threads außerhalb des WPF-Kontextes.

Sie können es durch DispatcherTimer ersetzen.

Wenn ein System.Timers.Timer in einer WPF-Anwendung verwendet wird, ist es erwähnenswert, dass der System.Timers.Timer auf einem anderen Thread läuft dann die Benutzerschnittstelle (UI) Gewinde. Um auf Objekte im UI-Thread zugreifen zu können, müssen Sie die Operation mit Invoke oder BeginInvoke auf den Dispatcher des UI-Threads setzen. Gründe für die Verwendung eines DispatcherTimers im Gegensatz zu einem System.Timers.Timer sind, dass der DispatcherTimer im selben Thread wie der Dispatcher ausgeführt wird und eine DispatcherPriority für den DispatcherTimer festgelegt werden kann.

Außerdem müssen Sie mit Wiedereintritt behandeln, wie bei System.Threading.Timer, weil ein Cpu gebunden Betrieb noch die vorherige Zecke Verarbeitung werden konnte.

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()); 
     } 
    } 
} 

Wenn Sie eine precise Kontrolle über müssen, was passiert, sind Sie besser dran mit Ereignissen Schlange stehen und sie unabhängig von Verarbeitung Anzeige:

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: Für Windows Forms Sie DispatcherTimer mit der ersetzen könnte System.Windows.Forms.Timer im zweiten Codebeispiel.

+0

Leider ist es nicht WPF-Anwendung, sondern Windows Forms, wie ich in Tag erwähnt. – Pablo

+0

Dann könnten Sie nur eine Endlosschleife mit warten Task.Delay (10) aus dem Form.Load -Ereignis und Abfragen in einer Warteschlange, wie im zweiten Beispiel gezeigt. –

+1

Oder, noch besser, Sie könnten DispatcherTimer durch den [System.Windows.Forms.Timer] (https://msdn.microsoft.com/en-us/library/system.windows.forms.timer (v = vs. 110) .aspx) –

Verwandte Themen