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.
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. –
Entschuldigen Sie, ignorieren Sie den Teil über die Race Condition. Ich glaube ich irre mich, habe ein bisschen Code falsch verstanden. –
soll ich 'TaskScheduler.FromCurrentSynchronizationContext()' an Aktion übergeben? Aber dann kann ich async/await nicht verwenden und muss den Kontext selbst mit TPL ändern ... – Pablo