在运行时创建复杂的UserControl而不冻结GUI

时间:2013-05-15 15:02:39

标签: c# wpf multithreading backgroundworker dispatcher

我有一个非常复杂的UserControl需要在运行时创建。这个创建将GUI冻结大约5秒钟(这是不可接受的)。

我尝试将此操作移动到后台工作器中,最终得到此异常:

  

调用线程必须是STA,因为许多UI组件都需要这个。

我知道我无法使用MTA线程来创建UserControl / UI元素。我尝试使用BackgroundWorker和Dispatcher的组合,但它不起作用。

<小时/> 首先尝试

private void LetsGo() 
{
    var backgroundWorker = new BackgroundWorker();

    backgroundWorker.DoWork += backgroundWorker_DoWork;
    backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;

    backgroundWorker.RunWorkerAsync();
}

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(new Action(this.GenerateControlAsync), DispatcherPriority.Background);
}

private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        // Cancelled
    }
    else if (e.Error != null)
    {
        //Exception Thrown
    }
    else
    {
        //Completed
    }
}

private void GenerateControlAsync () {
    this.Control = new TimeConsumingUserControl();
}

上面的代码不起作用,方法this.GenerateControlAsync不会被执行。

<小时/> 第二次尝试

private async void GenerateControl()
{
    this.Control = await Dispatcher.CurrentDispatcher.InvokeAsync<UserControl>(this.GenerateControlAsync);
}

private UserControl GenerateControlAsync()
{
    return new TimeConsumingUserControl();
}

此示例正在运行,但它会一直冻结GUI线程。

<小时/> 我正在使用WPF和.net Framework 4.5。

请注意,方法GenerateControlAsync()不是简单地创建UserControl的实例,涉及更多逻辑。

回答@ HighCore 的问题: 实际上,UserControl的XAML代码通过XSLT从xml文件转换而来,Codebehind是使用CodeDOM生成的。整个事情需要编译并包装到一个程序集中。我使用assembly.CreateInstance()来获取UserControl的一个实例。该行抛出引用的异常。在我的GUI中,我有一个ContentControl,它与我的ViewModel中的UserControl绑定。生成的数据(如需要转换的xml文件)将从Web服务中检索。

这就是为什么这种方法的执行需要的时间比人们预期的要长一些。

2 个答案:

答案 0 :(得分:1)

根据您对创建控件所涉及的所有步骤的描述,看起来您正在将大量工作集中在一起,而这些工作不需要在同一个线程上完成,并尝试在UI或背景线程。您应该在UI线程(实际的UserControl实例化)上执行必要的最少工作量,并在工作线程上执行其他所有操作。你应该做两个步骤而不是单个异步工作单元,而使用async-await非常简单。看起来应该更像这样:

var dynamicAssembly = await this.GenerateControlAssemblyAsync();
this.Control = this.GenerateControlFromAssembly(dynamicAssembly);

由于await的工作方式,第二行将自动在原始(UI)线程上运行,因此不需要任何Dispatcher次调用。在GenerateControlAssemblyAsync中,您应该使用Task.Run并执行其中的所有其他代码。 GenerateControlFromAssembly除了实例化UC实例之外什么都不做。

答案 1 :(得分:1)

你需要对你的装配生成进行分块。您的程序集生成需要花费太多时间,您需要将所有内容放在另一个线程中,并且只在同一个线程中生成DIRECT Ui组件。

有一种很酷的方法可以从流中加载Xaml并以块的形式进行,而不会堵塞UI。看看:http://msdn.microsoft.com/en-us/library/aa346591.aspx