我有调用存储操作列表的计时器。我希望异步调用这些操作。所以我将CPU绑定操作包装到任务中,然后使async / await运行。但是,它没有更新组合框。显然,上下文不会切换回UI,但我不明白为什么以及我应该做些什么来解决它。
主要形式的代码:
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);
});
}
计时器类:
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();
}
}
这是我用来执行操作的包装类:
public class ActionWrapper
{
public bool Periodic { get; set; }
public Func<Task> MyAction { get; set; }
}
答案 0 :(得分:1)
并不是它没有切换回来,它首先没有在UI线程上启动,因为你正在使用System.Threading.Timer
来处理WPF中的线程池线程上的滴答上下文。
您可以将其替换为DispatcherTimer。
如果在WPF应用程序中使用System.Timers.Timer,则值得注意的是System.Timers.Timer在与用户界面(UI)线程不同的线程上运行。为了访问用户界面(UI)线程上的对象,必须使用Invoke或BeginInvoke将操作发布到用户界面(UI)线程的Dispatcher上。使用与System.Timers.Timer相对的DispatcherTimer的原因是 DispatcherTimer在与Dispatcher 相同的线程上运行,并且可以在DispatcherTimer上设置DispatcherPriority。
此外,您需要处理重新入口,与System.Threading.Timer
相同,因为Cpu绑定操作仍然可以处理上一个滴答。
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());
}
}
}
如果您需要precise
控制发生的事情,最好排队事件并独立于处理显示事件:
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);
}
}
}
更新: 对于Windows窗体,您可以使用第二个代码示例中的System.Windows.Forms.Timer替换DispatcherTimer。