UI使用线程后甚至冻结

时间:2014-06-21 19:08:57

标签: c# wpf multithreading

我需要执行一些高级数学计算而不冻结我的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#的线程模型。任何帮助将受到高度赞赏。 感谢。

3 个答案:

答案 0 :(得分:1)

您不应该在并行化构造中使用Dispatcher.Invoke。至少不到这种程度。

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

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

和this.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

答案 1 :(得分:1)

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

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在另一个线程上运行耗时的操作,并在 RunWorkerCompleted 事件中使用UI更新代码(设置ItemsSource) BW在UI线程上运行。

答案 2 :(得分:0)

卸载CPU边界的推荐方法是通过Task Parallel Library。您可以使用Dispatcher.Invokeawait的组合,或将Task.RunTask.Run结合使用,而不是显式调用ContinueWith。请注意,在使用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不接受必须在延续内添加的计数异常处理,而await将在Task.Run内发生异常时抛出