UI冻结,即使在使用线程之后

本文关键字:线程 之后 UI 冻结 | 更新日期: 2023-09-27 18:09:47

我需要在不冻结WPF UI的情况下执行一些崇高的数学计算。这是我为实现它而写的代码。

new Thread(() =>
{
    ThreadPool.QueueUserWorkItem((state) =>
    {
        Dispatcher.Invoke(new Action(() =>
        {
            double x1_min = Convert.ToDouble(txt_x1_min.Text);
            double x1_max = Convert.ToDouble(txt_x1_max.Text);
            double x2_min = Convert.ToDouble(txt_x2_min.Text);
            double x2_max = Convert.ToDouble(txt_x2_max.Text);
            int iter = Convert.ToInt16(txtIterations.Text);
            //Data Defining and Computing
            obj.Run(x1_min, x1_max, x2_min, x2_max, iter);
            myDataGrid.ItemsSource = PSOLib.table.DefaultView;
            Minima.Text = string.Format("{0,0:0.000} ", PSOLib.min);
        }));
    });
}).Start();

我读过很多关于如何解冻它的其他主题,但我真的不太习惯C#的线程模型。如有任何帮助,我们将不胜感激。谢谢

UI冻结,即使在使用线程之后

将其放在Dispatcher上将仅在UI线程上运行代码,最终将冻结UI。所以,只将UI stuf放在UI线程上,并在辅助线程上运行耗时的操作。

double x1_min = Convert.ToDouble(txt_x1_min.Text);
double x1_max = Convert.ToDouble(txt_x1_max.Text);
double x2_min = Convert.ToDouble(txt_x2_min.Text);
double x2_max = Convert.ToDouble(txt_x2_max.Text);
int iter = Convert.ToInt16(txtIterations.Text);
ThreadPool.QueueUserWorkItem((state) =>
    {
        //Data Defining and Computing
        obj.Run(x1_min, x1_max, x2_min, x2_max, iter);
        Dispatcher.Invoke(new Action(() =>
        {
            myDataGrid.ItemsSource = PSOLib.table.DefaultView;
            Minima.Text = string.Format("{0,0:0.000} ", PSOLib.min);
        }));
    });

您还可以使用BackgroundWorker在另一个线程上运行耗时的操作,以及在UI线程本身上运行的BW的RunWorkerCompleted事件中的UI更新代码(设置ItemsSource(

您不应该使用Dispatcher。在并行化构造内部调用。至少没有达到这样的程度。

在原始代码中,工作线程中的代码所做的唯一实际工作就是向Dispatcher添加一些Action。您的线程实际上没有进行任何计算——它只是向Dispatcher引擎添加了一个任务并结束,比如:

ThreadPool.QueueUserWorkItem((state) =>
    { 
        Dispatcher.Invoke(new Action(this.DoEverything));
    });

还有这个。DoEverything将不会在工作线程上执行,而是在UI后台线程上执行。因此,使用工作线程的任何好处都是无效的。

您应该完成所有计算,然后再更改UI。否则,如果您在没有任何并行化的情况下直接使用它,它将不会有更多的响应。

 // Complete all interactions with UI to get data before using another thread
    double x1_min = Convert.ToDouble(txt_x1_min.Text);
    double x1_max = Convert.ToDouble(txt_x1_max.Text);
    double x2_min = Convert.ToDouble(txt_x2_min.Text);
    double x2_max = Convert.ToDouble(txt_x2_max.Text);
    int iter = Convert.ToInt16(txtIterations.Text);
    ThreadPool.QueueUserWorkItem((state) =>
        {
            //Data Defining and Computing that are not dependent on any UI elements
            obj.Run(x1_min, x1_max, x2_min, x2_max, iter);     
            var data = PSOLib.table.DefaultView;
            Dispatcher.Invoke(new Action(() =>
            {
                //Update the UI
                myDataGrid.ItemsSource = data;
                Minima.Text = string.Format("{0,0:0.000} ", PSOLib.min);
            }));
        });

实际上,您可以尝试其他一些并行化构造,如Tasks:

卸载CPU边界工作的推荐方式是通过Task Parallel Library。您可以使用awaitTask.Run的组合,或者将Task.RunContinueWith一起使用,而不是显式调用Dispatcher.Invoke。注意,当使用await时,您需要用async关键字标记您的方法,并让它返回Task

await:

double x1_min = Convert.ToDouble(txt_x1_min.Text);
double x1_max = Convert.ToDouble(txt_x1_max.Text);
double x2_min = Convert.ToDouble(txt_x2_min.Text);
double x2_max = Convert.ToDouble(txt_x2_max.Text);
int iter = Convert.ToInt16(txtIterations.Text)
var cpuConsumingTask = await Task.Run(() => obj.Run(x1_min, x1_max, x2_min, x2_max, iter));
myDataGrid.ItemsSource = PSOLib.table.DefaultView;
Minima.Text = string.Format("{0,0:0.000} ", PSOLib.min);

ContinueWith:

double x1_min = Convert.ToDouble(txt_x1_min.Text);
double x1_max = Convert.ToDouble(txt_x1_max.Text);
double x2_min = Convert.ToDouble(txt_x2_min.Text);
double x2_max = Convert.ToDouble(txt_x2_max.Text);
int iter = Convert.ToInt16(txtIterations.Text)
var cpuConsumingTask = Task.Run(() => obj.Run(x1_min, x1_max, x2_min, x2_max, iter)).ContinueWith(task => 
{
   myDataGrid.ItemsSource = PSOLib.table.DefaultView;
   Minima.Text = string.Format("{0,0:0.000} ", PSOLib.min);
},    TaskScheduler.FromCurrentSynchronizationContext());

为了可读性和易用性,我肯定会使用前者。请注意,ContinueWith不接受计数异常处理,该处理必须添加到延续中,而如果Task.Run 内部发生异常,则await将抛出