Passing Wpf Objects Between Threads (With Source Code)

本文探讨了在WPF应用程序中解决跨线程传递UI组件的问题。通过使用自定义线程并结合Dispatcher进行数据交换,最终采用XAML读写方式成功实现FixedDocument对象的线程间传递。
Passing Wpf Objects Between Threads (With Source Code)

When working on yaTImer's new report engine I got myself into a bit of a problem, I'm blogging about it because I couldn't find an answer on the web and I can't believe I'm the only one with this problem, so I hope someone will find my solution helpful (or maybe suggest a better one).

My reports engine generates a FixedDocument that I can print or show to the user, because the report can contain a lot of information generating the document can potentially take some time, so I went for the easy solution and dropped a BackgroundWorker into the code.

So this:

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _documentViewer.Document = GenerateDocument();
}

Becomes this:

// WARNING: THIS CODE DOESN'T WORK
private BackgroundWorker _backgroundWorker;
private FixedDocument _result;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundWorker = new BackgroundWorker();
   _backgroundWorker.DoWork += DoGenerateDocument;
   _backgroundWorker.RunWorkerCompleted += FinishedGenerating;
   _backgroundWorker.RunWorkerAsync();
}

void DoGenerateDocument(object sender, DoWorkEventArgs e)
{
   _result = GenerateDocument(); // this line throws an exception
}

void FinishedGenerating(object sender, RunWorkerCompletedEventArgs e)
{
   _documentViewer.Document = _result;
}

While the code was very elegant it doesn't work, I got an exception with a very nice error message: " The calling thread must be STA, because many UI components require this."

Fortunately the error message is very clear and it's easy to find the solution on the web, you can't use Wpf objects from a thread-pool thread (or from a BackgroundWorker) you have to create your own thread, the code to create the thread is very simple:

Thread _backgroundThread;
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();

Now because we no longer have BackgroundWorker events we have to write our own code to pass data between threads, this is easy to do with the Dispacher class

// WARNING: THIS CODE DOESN'T WORK
private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<FixedDocument> )FinishedGenerating,
      result);
}

void FinishedGenerating(FixedDocument result)
{
   _documentViewer.Document = result; // now this line throws an exception
}
This code is written inside a Wpf window, and the Window class has a Dispacher property that returns the dispatcher for the thread that created the window, if you're code doesn't use a window then you need to get the dispatcher associated with the thread you want to communicate with, this is done by reading Dispacher.CurrentDispacher from that thread.

But this code also doesn't work, when I pass the FixedDocument into the document viewer I get this lovely exception: "The calling thread cannot access this object because a different thread owns it."

This is where things get complicated, I couldn't find any way to marshal the document into the UI thread.

Since this is a fixed document the natural thing to do is to save it into an XPS file (XPS is the Wpf version of PDF) and then read it from the other thread, I've tried doing this with a MemoryStream (I don't want to create an actual file) and I discovered a problem with this approach – apparently you can't read an XPS from a memory stream, you need an actual file.

At that point I got the clever idea of using XAML, you can read XAML from a memory stream and the code to read and write XAML is actually simpler then the XPS code, here is the final code using XamlReader and XamlWriter:

private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   MemoryStream stream = new MemoryStream();
   XamlWriter.Save(result, stream);
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<MemoryStream> )FinishedGenerating,
      stream);
}

void FinishedGenerating(MemoryStream stream)
{
   stream.Seek(0, SeekOrigin.Begin);
   FixedDocument result = (FixedDocument)XamlReader.Load(stream);
   _documentViewer.Document = result;
}

This trick should use with any Wpf object, not just FixedDocument.

源码直接下载地址: https://pan.quark.cn/s/95437fdf229e Intel I-219V网卡驱动是一款专门为Intel的I-219V千兆以太网控制器而研发的驱动程序,其主要作用在于保障在Ubuntu 16.04操作系统环境下的正常运作以及优化系统性能。Intel I-219V作为一款广泛应用的内置网络接口控制器(NIC),常被集成在台式机及笔记本电脑的主板上,负责提供高速的网络连接服务。Intel公司所提供的e1000e驱动是与此硬件相配套的开源驱动解决方案,其中版本3.3.5.3是专门针对该硬件设备的定制版本。此驱动包含了不可或缺的源代码部分,赋予开发者和系统管理者按照特定需求进行编译和定制的权限,从而能够适应多样化的系统配置或针对特定情形进行问题解决。源代码的可用性同样表明用户有能力依据Linux内核的更新情况来升级驱动,确保与最新技术标准的兼容性。在Ubuntu 16.04系统中成功编译的驱动意味着它已经通过了严苛的测试流程,并能够与该版本的Linux内核实现良好兼容。Ubuntu 16.04,其代号为Xenial Xerus,是一个长期支持(LTS)的版本,因此对于那些追求系统稳定性和安全保障的用户群体而言具有特殊的意义。驱动程序的兼容性保障了I-219V网卡能够在该系统平台上实现无缝运行,提供稳定可靠的网络连接,这既包括局域网(LAN)的连接,也可能涵盖通过Wi-Fi桥接实现的无线网络连接。驱动程序的核心职责涵盖了网络接口的初始化与管理、数据包的接收与发送处理,以及错误检测与纠正功能的执行。在Linux操作系统架构中,驱动通常以模块的形式加载至内核之中,这种设计允许在非必要时期进行卸载操作,以此来有效节省系统资源。e1000e驱...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值