C# Task.Run 减缓了我的 WPF 程序

5

简短故事: 我正在设计软件,用于验证CAD应用程序中的某些内容。当我尝试使用并行处理时,在WPF中会变得非常慢。

长故事: 我的软件向我制作的库(.dll)提出问题,该库与我的CAD软件(E3.series from Zuken)通信。

我的软件最初是在WinForms中制作的,一切都很好,但我认为界面将因绑定而受益匪浅,所以我进行了翻译,但发现它的速度要慢得多。因此,我做了两个小项目,找到了它变得更慢的位置。以下是结果,但我找不到原因:

这段代码与Winforms一样快(3.5秒):

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    Stopwatch timer = new Stopwatch();
    timer.Start();
    List<Wire> wires = e3.Project.Wires;
    timer.Stop();
    MessageBox.Show("WPF " + wires.Count + " in " + timer.Elapsed);
}

但是当我这样做时,它需要(12秒):

private void Grid_Loaded(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
        {
            Stopwatch timer = new Stopwatch();
            timer.Start();
            List<Wire> wires = e3.Project.Wires;
            timer.Stop();
            MessageBox.Show("WPF " + wires.Count + " in " + timer.Elapsed);
        });
}

这和 BackgroundWorker 完全相同。

我需要使用 Task.RunParallel.ForEach,因为我有很多长时间的任务需要并行化。当我改用 Parallel.ForEach 时,我的 WinForms 应用程序变得快了5-6倍,所以我不想失去这个优势而切换到 WPF。

我发现我的 CPU 在空的 WPF 窗口下运行得更高,所以可能是我的 CAD 应用程序没有足够的 CPU 来工作并回答我的 dll?

我认为我在 VS2015 中使用 CPU 使用情况分析器发现的是,由于我的应用程序,从我的库到我的 CAD 软件的 COM 调用变得非常慢。


1
由于绑定的支持,使用WPF会对您非常有益。但是请注意,Winforms同样支持绑定。 - Fabio
2个回答

2
一个WinForm和WPF应用程序的UI都是在静态线程单元(STA)模型下运行。 您提到使用Task.Run或Background worker会降低性能。由于这两个都创建了多线程单元(MTA)线程,问题可能是当COM组件在STA中创建时需要进行封送。有关更多信息,请参见理解和使用COM线程模型中的“混合模型开发”部分。
您可以尝试创建一个STA线程并在其中运行代码。
Thread t = new Thread(ThreadWorkMethod);
t.SetApartmentState(ApartmentState.STA);
t.Start();

1
由于我不知道如何等待此线程完成,因此无法按照您的要求执行,但最终在新线程中创建E3对象代替之前创建并在新线程中使用它解决了我的问题。 - user3704628
1
@user3704628,通常情况下,您不需要等待线程完成,而是提供一种机制,例如引发事件来表示其完成。另一个选项是传递一个委托到UI线程上执行的方法;这实际上是一种更直接的方法,而不是一个仅仅是调用委托的糖果涂层的事件。无论如何,我很高兴得到反馈,因为我知道这种可能性,但从未经历过。如果您在原始帖子中提及CAD软件的名称,这也将有助于其他人。 - TnTinMn

0
首先,您不应该在线程池中调用MessageBox.Show,因为它们没有设置为用户界面线程。
其次,请确保e3.Project.Wires属性也不执行任何用户界面操作。
第三,尝试使用以下方法将工作排队到线程池...
ThreadPool.QueueUserWorkItem(ThreadProc, e3);

...像这样实现方法...

static void ThreadProc(Object stateInfo) 
{
    ??? e3 = (???)stateInfo;
    Stopwatch timer = new Stopwatch();
    timer.Start();
    List<Wire> wires = e3.Project.Wires;
    timer.Stop();
    Console.WriteLine("WPF " + wires.Count + " in " + timer.Elapsed);
}

显然,您需要将传入的参数转换为正确的类型。


通过这段代码,我成功地让我的WinForms和WPF Window一样慢(11秒)。我还发现e3.Project.Wires调用CAD软件的次数非常多,这是正常的,但是当我使用这种技术时,1次调用需要25毫秒而不是4毫秒,所以问题实际上是一个副作用,导致CAD软件响应变慢了6倍。 - user3704628
2
ThreadPool.QueueUserWorkItem is same as Task.Run without benefits of async-await - Fabio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接