繁体   English   中英

异步/等待不切换回UI线程

[英]async/await not switching back to UI thread

我有计时器,它正在调用已存储操作的列表。 我希望这些动作被异步调用。 因此,我将受CPU约束的操作包装到任务中,然后进行异步/等待操作。 但是,它没有更新组合框。 显然,上下文并没有切换回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; }
}

这并不是说它不会切换回去,也不是首先在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窗体,可以在第二个代码示例中将DispatcherTimer替换为System.Windows.Forms.Timer

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM