- 浏览: 315040 次
文章分类
- 全部博客 (308)
- Apple (1)
- MAC (6)
- iPhone (1)
- iOS (1)
- Adobe (3)
- Microsoft (23)
- Windows (12)
- WAP (2)
- CMPP (6)
- Eclipse (5)
- .NET (13)
- Router (3)
- ADO.NET (2)
- C# (11)
- WPF (3)
- SqlServer (4)
- Facebook (2)
- JDBC (1)
- Visual Studio (10)
- ActiveMQ (9)
- Java (112)
- Memcached (2)
- NoSQL (7)
- Hadoop (9)
- GlassFish (2)
- Linux (98)
- CentOS (58)
- Spring (10)
- Oracle (17)
- JBOSS (12)
- Webservice (4)
- EJB (7)
- DFS (1)
- Hibernate (9)
- Redis (3)
- Tomcat (5)
- Python (8)
- FastDFS (6)
- Nginx (6)
- RabbitMQ (0)
- Erlang (0)
- Lucene (8)
- Solr (7)
- MySQL (1)
- JavaScript (0)
最新评论
-
zhangy888:
你好,我正好也遇到了这个问题,按照您的设置有如下几个问题,请帮 ...
CXF SOAP 1.2 SOAP 1.1 问题 -
u011493586:
这个SUBMIT写的还不错,只是有的地方没看懂
CMPP发送超长短信息(JAVA版) -
u011493586:
...
CMPP发送超长短信息(JAVA版)
Windows Presentation Foundation (WPF) 旨在帮助开发人员解决线程处理的难题。这样,大多数 WPF 开发人员就不必编写使用多个线程的接口。由于多线程程序很复杂且难以调试,因此只要存在单线程解决方案,就应避免使用多个线程。
但是,无论体系结构多么完善,没有任何 UI 框架能够为每一类问题提供单线程解决方案。WPF 接近这一目标,但是在某些情况下,仍然可通过采用多个线程来提高user interface (UI) 响应度或应用程序性能。在讨论了一些背景材料后,本文将探讨其中一些情况,最后从较低层次进行一些详细讨论。
本主题包括下列各节。
通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。大多数应用程序都使用一个 UI 线程,但在某些情况下,最好使用多个线程。我们将在后面举例说明这一点。
UI 线程对一个名为 Dispatcher 的对象内的工作项进行排队。Dispatcher 基于优先级选择工作项,并运行每一个工作项,直到完成。每个 UI 线程都必须至少有一个 Dispatcher,并且每个Dispatcher 都只能在一个线程中执行工作项。
要构建响应速度快、且用户友好的应用程序,诀窍是减小工作项,以最大限度地提高 Dispatcher 吞吐量。 这样,工作项将永远不会因为在 Dispatcher 队列中等待处理而失效。 输入与响应之间的任何可察觉的延迟都会使用户不快。
那么,WPF 应用程序应如何处理大型操作呢?如果您的代码涉及大型计算,或者需要查询某台远程服务器上的数据库,应怎么办呢?通常的办法是在单独的线程中处理大型操作,而专门让 UI 线程来负责处理 Dispatcher 队列中的工作项。当大型操作完成时,可以将结果报告给 UI 线程来显示。
一直以来,Windows 只允许创建 UI 元素的线程访问这些元素。这意味着负责某项长时间运行任务的后台线程无法更新已完成的文本框。Windows 这样做是为了确保 UI 组件的完整性。如果列表框的内容在绘制过程中被后台线程更新,那么该列表框看上去将会很奇怪。
WPF 使用一种内置互斥机制来强制执行这种协调。WPF 中的大多数类都派生自 DispatcherObject。DispatcherObject 在构造时存储对链接到当前所运行线程的 Dispatcher 的引用。实际上,DispatcherObject 与创建它的线程关联。在程序执行过程中,DispatcherObject 可以调用它的公共 VerifyAccess 方法。VerifyAccess 检查与当前线程关联的 Dispatcher,并将它与构造过程中存储的 Dispatcher 引用进行比较。如果两者不匹配,VerifyAccess 将引发异常。VerifyAccess 用于在每个属于 DispatcherObject 的方法的开头调用。
如果只有一个线程可以修改 UI,那么后台线程如何与用户交互呢?后台线程可以请求 UI 线程代表它执行操作。这是通过向 UI 线程的 Dispatcher 注册工作项来完成的。Dispatcher 类提供两个注册工作项的方法:Invoke 和 BeginInvoke。这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke 是异步的,将立即返回。
Dispatcher 按优先级对其队列中的元素进行排序。 向 Dispatcher 队列中添加元素时可指定 10 个级别。 这些优先级在 DispatcherPriority 枚举中维护。 有关 DispatcherPriority 级别的详细信息可以在 Windows SDK 文档中找到。
具有长时间运行计算的单线程应用程序
大多数graphical user interfaces (GUIs) 的大部分空闲时间都是因为等待响应用户交互而生成的事件而造成的。通过仔细地编程,可以积极地利用这一空闲时间,而不影响 UI 的响应度。WPF 线程模型不允许输入中断 UI 线程中正在进行的操作。这意味着您必须定期返回到 Dispatcher 来处理挂起的输入事件,以防止它们停滞。
请看下面的示例:
这是一个简单的应用程序,从 3 开始往上数,搜索质数。 当用户单击“Start”(开始)按钮时,搜索开始。 当程序找到一个质数时,则会用它的搜索结果更新用户界面。 用户可以随时停止搜索。
尽管此应用程序非常简单,但质数搜索可以无限地继续下去,这带来了一定的困难。 如果在按钮的 click 事件处理程序中处理了整个搜索,UI 线程将永远没有机会处理其他事件。UI 将无法响应输入或处理消息。它永远不会重新绘制,也永远不会响应按钮单击。
我们可以在一个单独的线程中执行质数搜索,但之后需要处理同步问题。 使用单线程方法,可以直接更新列出找到的最大质数的标签。
如果将计算任务分成多个易管理的块,就可以定期返回到 Dispatcher 来处理事件。 可以给 WPF 提供一个机会来重新绘制和处理输入。
在计算与事件处理之间划分处理时间的最佳方法是从 Dispatcher 中管理计算。 通过使用 BeginInvoke 方法,可以在从中提取 UI 事件的同一队列中安排质数检查。在本示例中,一次仅安排一个质数检查。完成该质数检查后,将立即安排下一次检查。此检查仅在挂起的 UI 事件经过处理后才会继续。
Microsoft Word 就是使用这一机制来完成拼写检查。拼写检查是利用 UI 线程的空闲时间在后台执行的。我们来看一看代码。
下面的示例演示创建用户界面的 XAML。
<Window x:Class="SDKSamples.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Prime Numbers" Width="260" Height="75" > <StackPanel Orientation="Horizontal" VerticalAlignment="Center" > <Button Content="Start" Click="StartOrStop" Name="startStopButton" Margin="5,0,5,0" /> <TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock> <TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock> </StackPanel> </Window>
<Window x:Class="SDKSamples.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Prime Numbers" Width="260" Height="75" > <StackPanel Orientation="Horizontal" VerticalAlignment="Center" > <Button Content="Start" Click="StartOrStop" Name="startStopButton" Margin="5,0,5,0" /> <TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock> <TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock> </StackPanel> </Window>
下面的示例演示代码隐藏。
Imports System Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Threading Imports System.Threading Namespace SDKSamples Partial Public Class MainWindow Inherits Window Public Delegate Sub NextPrimeDelegate() 'Current number to check Private num As Long = 3 Private continueCalculating As Boolean = False Public Sub New() MyBase.New() InitializeComponent() End Sub Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs) If continueCalculating Then continueCalculating = False startStopButton.Content = "Resume" Else continueCalculating = True startStopButton.Content = "Stop" startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber)) End If End Sub Public Sub CheckNextNumber() ' Reset flag. NotAPrime = False For i As Long = 3 To Math.Sqrt(num) If num Mod i = 0 Then ' Set not a prime flag to true. NotAPrime = True Exit For End If Next ' If a prime number. If Not NotAPrime Then bigPrime.Text = num.ToString() End If num += 2 If continueCalculating Then startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber)) End If End Sub Private NotAPrime As Boolean = False End Class End Namespace
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using System.Threading; namespace SDKSamples { public partial class Window1 : Window { public delegate void NextPrimeDelegate(); //Current number to check private long num = 3; private bool continueCalculating = false; public Window1() : base() { InitializeComponent(); } private void StartOrStop(object sender, EventArgs e) { if (continueCalculating) { continueCalculating = false; startStopButton.Content = "Resume"; } else { continueCalculating = true; startStopButton.Content = "Stop"; startStopButton.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new NextPrimeDelegate(CheckNextNumber)); } } public void CheckNextNumber() { // Reset flag. NotAPrime = false; for (long i = 3; i <= Math.Sqrt(num); i++) { if (num % i == 0) { // Set not a prime flag to true. NotAPrime = true; break; } } // If a prime number. if (!NotAPrime) { bigPrime.Text = num.ToString(); } num += 2; if (continueCalculating) { startStopButton.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.SystemIdle, new NextPrimeDelegate(this.CheckNextNumber)); } } private bool NotAPrime = false; } }
下面的示例演示 Button 的事件处理程序。
Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs) If continueCalculating Then continueCalculating = False startStopButton.Content = "Resume" Else continueCalculating = True startStopButton.Content = "Stop" startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber)) End If End Sub
private void StartOrStop(object sender, EventArgs e) { if (continueCalculating) { continueCalculating = false; startStopButton.Content = "Resume"; } else { continueCalculating = true; startStopButton.Content = "Stop"; startStopButton.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new NextPrimeDelegate(CheckNextNumber)); } }
除了更新 Button 上的文本外,此处理程序还负责通过向 Dispatcher 队列添加委托来调度第一次质数检查。 在此事件处理程序完成其工作后的一段时间内,Dispatcher 会选择此委派来执行。
前面已提到,BeginInvoke 是用于调度委托来执行的 Dispatcher 成员。 在这种情况下,选择 SystemIdle 优先级。 仅当没有重要的事件要处理时,Dispatcher 才会执行此委托。 UI 响应速度比数字检查更重要。我们还要传递一个表示数字检查例程的新委托。
Public Sub CheckNextNumber() ' Reset flag. NotAPrime = False For i As Long = 3 To Math.Sqrt(num) If num Mod i = 0 Then ' Set not a prime flag to true. NotAPrime = True Exit For End If Next ' If a prime number. If Not NotAPrime Then bigPrime.Text = num.ToString() End If num += 2 If continueCalculating Then startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber)) End If End Sub Private NotAPrime As Boolean = False
public void CheckNextNumber() { // Reset flag. NotAPrime = false; for (long i = 3; i <= Math.Sqrt(num); i++) { if (num % i == 0) { // Set not a prime flag to true. NotAPrime = true; break; } } // If a prime number. if (!NotAPrime) { bigPrime.Text = num.ToString(); } num += 2; if (continueCalculating) { startStopButton.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.SystemIdle, new NextPrimeDelegate(this.CheckNextNumber)); } } private bool NotAPrime = false;
此方法检查下一个奇数是否是质数。 如果是质数,此方法将直接更新 bigPrimeTextBlock 来反映搜索结果。 由于计算发生在用于创建组件的同一线程中,因此可以执行此操作。 如果选择对计算使用单独的线程,则必须使用一种更复杂的同步机制,并在 UI 线程中执行更新。接下来我们将演示这一情况。
有关此示例的完整源代码,请参见 Single-Threaded Application with Long-Running Calculation Sample(长时间运行计算的单线程应用程序示例)
用后台线程处理阻止操作
在图形应用程序中处理阻止操作很困难。 我们不希望从事件处理程序中调用阻止方法,因为这样应用程序看上去好像已冻结。 可以使用一个单独的线程来处理这些操作,但完成后必须与 UI 线程同步,因为不能从辅助线程直接修改 GUI。可以使用 Invoke 或 BeginInvoke 向 UI 线程的 Dispatcher 中插入委托。最终,这些委托将以修改 UI 元素的权限来执行。
在本示例中,模拟检索天气预报的远程过程调用。 使用一个单独的辅助线程来执行此调用,并在完成后在 UI 线程的 Dispatcher 中调度一个更新方法。
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; using System.Threading; namespace SDKSamples { public partial class Window1 : Window { // Delegates to be used in placking jobs onto the Dispatcher. private delegate void NoArgDelegate(); private delegate void OneArgDelegate(String arg); // Storyboards for the animations. private Storyboard showClockFaceStoryboard; private Storyboard hideClockFaceStoryboard; private Storyboard showWeatherImageStoryboard; private Storyboard hideWeatherImageStoryboard; public Window1(): base() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // Load the storyboard resources. showClockFaceStoryboard = (Storyboard)this.Resources["ShowClockFaceStoryboard"]; hideClockFaceStoryboard = (Storyboard)this.Resources["HideClockFaceStoryboard"]; showWeatherImageStoryboard = (Storyboard)this.Resources["ShowWeatherImageStoryboard"]; hideWeatherImageStoryboard = (Storyboard)this.Resources["HideWeatherImageStoryboard"]; } private void ForecastButtonHandler(object sender, RoutedEventArgs e) { // Change the status image and start the rotation animation. fetchButton.IsEnabled = false; fetchButton.Content = "Contacting Server"; weatherText.Text = ""; hideWeatherImageStoryboard.Begin(this); // Start fetching the weather forecast asynchronously. NoArgDelegate fetcher = new NoArgDelegate( this.FetchWeatherFromServer); fetcher.BeginInvoke(null, null); } private void FetchWeatherFromServer() { // Simulate the delay from network access. Thread.Sleep(4000); // Tried and true method for weather forecasting - random numbers. Random rand = new Random(); String weather; if (rand.Next(2) == 0) { weather = "rainy"; } else { weather = "sunny"; } // Schedule the update function in the UI thread. tomorrowsWeather.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.Normal, new OneArgDelegate(UpdateUserInterface), weather); } private void UpdateUserInterface(String weather) { //Set the weather image if (weather == "sunny") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "SunnyImageSource"]; } else if (weather == "rainy") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "RainingImageSource"]; } //Stop clock animation showClockFaceStoryboard.Stop(this); hideClockFaceStoryboard.Begin(this); //Update UI text fetchButton.IsEnabled = true; fetchButton.Content = "Fetch Forecast"; weatherText.Text = weather; } private void HideClockFaceStoryboard_Completed(object sender, EventArgs args) { showWeatherImageStoryboard.Begin(this); } private void HideWeatherImageStoryboard_Completed(object sender, EventArgs args) { showClockFaceStoryboard.Begin(this, true); } } }
下面给出了一些值得注意的细节。
-
创建按钮处理程序
private void ForecastButtonHandler(object sender, RoutedEventArgs e) { // Change the status image and start the rotation animation. fetchButton.IsEnabled = false; fetchButton.Content = "Contacting Server"; weatherText.Text = ""; hideWeatherImageStoryboard.Begin(this); // Start fetching the weather forecast asynchronously. NoArgDelegate fetcher = new NoArgDelegate( this.FetchWeatherFromServer); fetcher.BeginInvoke(null, null); }
当单击按钮时,显示时钟图并开始显示它的动画效果。 禁用该按钮, 在一个新线程中调用 FetchWeatherFromServer 方法,然后返回,这样 Dispatcher 就可以在我们等待收集天气预报时处理事件。
-
获取天气预报
private void FetchWeatherFromServer() { // Simulate the delay from network access. Thread.Sleep(4000); // Tried and true method for weather forecasting - random numbers. Random rand = new Random(); String weather; if (rand.Next(2) == 0) { weather = "rainy"; } else { weather = "sunny"; } // Schedule the update function in the UI thread. tomorrowsWeather.Dispatcher.BeginInvoke( System.Windows.Threading.DispatcherPriority.Normal, new OneArgDelegate(UpdateUserInterface), weather); }
为简单起见,此示例中实际没有任何网络代码。 通过使新线程休眠四秒钟来模拟网络访问的延迟。 此时,原始的 UI 线程仍然正在运行并响应事件。为了对此进行说明,我们使一个动画保持运行,并使最小化和最大化按钮也继续工作。
当延迟结束,并且我们已随机选择了天气预报时,是时候向 UI 线程返回报告了。为此,我们在 UI 线程中使用该线程的 Dispatcher 安排一个对 UpdateUserInterface 的调用。我们将一个描述天气的字符串传递给安排的此方法调用。
-
更新 UI
private void UpdateUserInterface(String weather) { //Set the weather image if (weather == "sunny") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "SunnyImageSource"]; } else if (weather == "rainy") { weatherIndicatorImage.Source = (ImageSource)this.Resources[ "RainingImageSource"]; } //Stop clock animation showClockFaceStoryboard.Stop(this); hideClockFaceStoryboard.Begin(this); //Update UI text fetchButton.IsEnabled = true; fetchButton.Content = "Fetch Forecast"; weatherText.Text = weather; }
当 UI 线程中的 Dispatcher 有时间时,会对 UpdateUserInterface 执行预定调用。此方法停止时钟动画并选择一个图像来描述天气。它显示此图像并还原“fetch forecast”(获取预报)按钮。
多个窗口,多个线程
一些 WPF 应用程序需要多个高级别窗口。一个线程/Dispatcher 组合管理多个窗口是完全可以接受的,但有时使用多个线程可以更出色地完成工作。如果其中一个窗口有可能独占该线程,那么采用多个线程就更有必要。
Windows 资源管理器就是以这种方式工作的。每个新的资源管理器窗口都属于原始进程,但是在独立线程的控制下创建的。
使用 WPF Frame 控件可以显示网页。我们可以轻松地创建一个简单的 Internet Explorer 替代控件。 首先介绍一个重要的功能,即打开新资源管理器窗口的功能。当用户单击“new window”(新建窗口)按钮时,将在一个单独的线程中启动窗口的一个副本。这样,一个窗口中长时间运行的操作或阻止性操作就不会锁定所有其他窗口。
事实上,Web 浏览器模型有它自己的复杂线程模型。 我们选择它是因为大多数读者对它都很熟悉。
下面的示例演示代码。
<Windowx:Class="SDKSamples.Window1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MultiBrowse"Height="600"Width="800"Loaded="OnLoaded"><StackPanelName="Stack"Orientation="Vertical"><StackPanelOrientation="Horizontal"><ButtonContent="New Window"Click="NewWindowHandler"/><TextBoxName="newLocation"Width="500"/><ButtonContent="GO!"Click="Browse"/></StackPanel><FrameName="placeHolder"Width="800"Height="550"></Frame></StackPanel></Window>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Threading; using System.Threading; namespace SDKSamples { publicpartialclass Window1 : Window { public Window1() : base() { InitializeComponent(); } privatevoid OnLoaded(object sender, RoutedEventArgs e) { placeHolder.Source = new Uri("http://www.msn.com"); } privatevoid Browse(object sender, RoutedEventArgs e) { placeHolder.Source = new Uri(newLocation.Text); } privatevoid NewWindowHandler(object sender, RoutedEventArgs e) { Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint)); newWindowThread.SetApartmentState(ApartmentState.STA); newWindowThread.IsBackground = true; newWindowThread.Start(); } privatevoid ThreadStartingPoint() { Window1 tempWindow = new Window1(); tempWindow.Show(); System.Windows.Threading.Dispatcher.Run(); } } }
在本语境中,这些代码中的以下线程片段最有意义:
当单击“new window”(新建窗口)按钮时,调用此方法。 它创建一个新线程并以异步方式启动它。
此方法是新线程的起点。 我们在此线程的控制下创建一个新窗口。 WPF 自动创建一个新的 Dispatcher 以管理新线程。要使该窗口起作用,只需启动 Dispatcher 即可。
使用线程编写组件
《Microsoft .NET Framework 开发人员指南》介绍了组件向其客户端公开异步行为的一种模式(请参见基于事件的异步模式概述)。例如,假定我们希望将 FetchWeatherFromServer 方法打包到一个可重用的非图形组件中。如果采用标准的 Microsoft .NET Framework 模式,那么代码应与下面的内容类似。
public class WeatherComponent : Component { //gets weather: Synchronous public string GetWeather() { string weather = ""; //predict the weather return weather; } //get weather: Asynchronous public void GetWeatherAsync() { //get the weather } public event GetWeatherCompletedEventHandler GetWeatherCompleted; } public class GetWeatherCompletedEventArgs : AsyncCompletedEventArgs { public GetWeatherCompletedEventArgs(Exception error, bool canceled, object userState, string weather) : base(error, canceled, userState) { _weather = weather; } public string Weather { get { return _weather; } } private string _weather; } public delegate void GetWeatherCompletedEventHandler(object sender, GetWeatherCompletedEventArgs e);
GetWeatherAsync 将使用前面介绍的一种技术(如创建后台线程)来异步执行工作,同时不阻止调用线程。
此模式的最重要部分之一是最初在调用方法名称Async 方法的线程上调用方法名称Completed 方法。 通过存储 CurrentDispatcher,您可以使用 WPF 轻松地实现这一点。但是,之后只能在 WPF应用程序中使用该非图形组件,而不能在 Windows Forms或 ASP.NET 程序中使用该组件。
DispatcherSynchronizationContext 类可满足这一需求。可以将该类视为还使用其他 UI 框架的 Dispatcher 的简化版本。
public class WeatherComponent2 : Component { public string GetWeather() { return fetchWeatherFromServer(); } private DispatcherSynchronizationContext requestingContext = null; public void GetWeatherAsync() { if (requestingContext != null) throw new InvalidOperationException("This component can only handle 1 async request at a time"); requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current; NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer); // Launch thread fetcher.BeginInvoke(null, null); } private void RaiseEvent(GetWeatherCompletedEventArgs e) { if (GetWeatherCompleted != null) GetWeatherCompleted(this, e); } private string fetchWeatherFromServer() { // do stuff string weather = ""; GetWeatherCompletedEventArgs e = new GetWeatherCompletedEventArgs(null, false, null, weather); SendOrPostCallback callback = new SendOrPostCallback(DoEvent); requestingContext.Post(callback, e); requestingContext = null; return e.Weather; } private void DoEvent(object e) { //do stuff } public event GetWeatherCompletedEventHandler GetWeatherCompleted; public delegate string NoArgDelegate(); }
嵌套泵
有时锁定 UI 线程是完全不可行的。我们可以考虑 MessageBox 类的 Show 方法。Show 只有在用户单击“确定”之后才会返回。但是它创建一个窗口,该窗口必须有消息循环才能进行交互。我们在等待用户单击“确定”,而原始应用程序窗口不响应用户输入。但是它会继续处理绘制消息。原始窗口在被遮盖和显示时会重新进行自我绘制。
必须有某个线程来负责消息框窗口。 WPF 即可为消息框窗口创建新线程,但此线程将无法绘制原始窗口中已禁用的元素(请回忆前面关于互斥的介绍)。WPF 改为使用一种嵌套的消息处理系统。Dispatcher 类包含一个名为 PushFrame 的特殊方法,该方法存储应用程序的当前执行点,然后开始一个新的消息循环。当嵌套的消息循环结束时,执行将在最初的 PushFrame 调用之后继续。
在这种情况下,PushFrame 在调用 MessageBox.Show 时保持程序上下文,并启动一个新的消息循环来重新绘制后台窗口,同时处理消息框窗口中的输入。 当用户单击“确定”并清除弹出窗口时,嵌套循环退出,控制在调用 Show 后继续。
失效的路由事件
当引发了事件时,WPF 中的路由事件系统会通知整个树。
<Canvas MouseLeftButtonDown="handler1" Width="100" Height="100" > <Ellipse Width="50" Height="50" Fill="Blue" Canvas.Left="30" Canvas.Top="50" MouseLeftButtonDown="handler2" /> </Canvas>
当在椭圆上按鼠标左键时,将执行 handler2。 在 handler2 结束后,事件将传递到 Canvas 对象,后者使用 handler1 来处理它。 只有当 handler2 未显式地将事件对象标记为已处理时,才会发生这种情况。
handler2 可能需要大量的时间来处理此事件。 handler2 使用 PushFrame 开始的嵌套消息循环可能在数小时内都不会返回。 如果在此消息循环完成时 handler2 仍未将事件标记为已处理,那么即便事件已经非常陈旧,仍将沿树向上传递。
重新进入和锁定
common language runtime (CLR) 的锁定机制与大家想象的不完全一样;您可能想象一个线程在请求锁时会完全停止操作。事实上,该线程会继续接收和处理高优先级消息。这有助于防止死锁,并使界面作出尽可能小的响应,但也引来了发生小 Bug 的可能性。在大多数情况下您不需要对此有任何了解,但在极少数情况下(通常涉及 Win32 窗口消息或 COM STA 组件),需要对此加以注意。
大多数界面在构建时都并未考虑线程安全性,因为开发人员在开发时假定绝不会有一个以上的线程访问 UI。在这种情况下,单个线程可能会在意外的时间进行环境更改,从而导致一些不好的影响,但 DispatcherObject 互斥机制应该可以予以解决。请考虑以下伪代码:
大多数情况下这是正常的,但在 WPF 中,某些情况下这种意外重新进入确实会导致出现问题。因此,在某些关键时刻,WPF 会调用 DisableProcessing,这会改变该线程的锁定指令,使它使用 WPF 非重入锁,而不是使用普通的 CLR 锁。
那么为什么 CLR 团队会选择这样的行为呢?因为它必须处理 COM STA 对象和终止进程。当对对象进行垃圾回收时,会在专门的终结器线程(而不是 UI 线程)上运行它的 Finalize 方法。问题就在这里:在 UI 线程上创建的 COM STA 对象只能在 UI 线程上释放。CLR 执行 BeginInvoke 的等效功能(在这种情况下使用 Win32 的 SendMessage)。但是,如果 UI 线程繁忙,则终结器线程将停止,COM STA 对象无法释放,这样会导致严重的内存泄漏。因此,CLR 团队进行严格的调用来使锁按预期的方式工作。
WPF 的任务是避免意外的重新进入,不重新引入内存泄漏。这就是我们在任何位置都不阻止重新进入的原因。
发表评论
-
C#调用C++的库 P/Invoke工具集
2015-04-28 15:06 1297p/Invoke可以使用工具辅助自动生成,以减少混淆 ... -
VisualSVN 破解
2014-10-08 17:02 1068首先,去VisualSVN官网下载最新版本。 传送门: h ... -
C#的Dictionary值排序和SortedDictionary键排序
2013-03-25 08:43 2888对一个Dictionary<TKey, TValue& ... -
C# Invalidate() Update() Refresh()的区别
2012-12-02 17:41 1438Control.Invalidate方法:使控件的特定区域无效 ... -
WPF导航在page中的实现方法
2012-08-15 10:23 1605WPF导航的实现方法有很多种。我们在文章中通过各种代码示例 ... -
enum : C#中的枚举类型,原来有这么多用法
2012-07-26 15:58 1444今天来的时候打开了MSDN扫了一眼C#里的枚举类型~~ 蓦然发 ... -
资源文件在国际化中的运用(WinForm)
2012-04-19 10:29 1097国际化的软件往往需要多种语言资源,如何在C#的WinFo ... -
C#保存文件或读取数据库文件 另存为
2012-04-18 09:51 2021/// <summary> /// ... -
WPF:RoutedUICommand和MenuItem
2012-04-16 10:51 1707把RoutedUICommand放在MenuIte ... -
C#自定义快捷键实现介绍
2012-04-16 10:33 1292这篇文章以按下Ctrl+Shift+0实现显示桌面为例, ... -
C# using语句使用心得总结
2012-04-11 12:04 1086这里是关于C# using语句使用心得总结,us ...
相关推荐
总之,`DispatcherObject` 和 WPF线程模型是WPF应用程序多线程编程的基础。了解它们的工作原理,能够帮助开发者编写出更加高效、稳定和响应式的应用。在实际开发中,要时刻注意线程安全,正确使用`Dispatcher` 进行...
为了解决这个问题,WPF引入了多线程技术,允许开发者在后台线程处理耗时任务,然后通过特定的方式安全地更新UI。 标题“WPF 使用线程更新UI”主要涉及到以下几个关键知识点: 1. **后台线程与主线程**:主线程是...
- **线程模型**:WPF应用的主线程是UI线程,负责处理用户交互和绘制UI。后台线程则用于执行非UI任务,如数据加载、计算等。 - **线程安全问题**:由于.NET Framework的UI控件不支持多线程访问,后台线程直接修改UI会...
在处理多线程应用时,由于WPF的UI元素只能在创建它们的UI线程(主线程)中进行操作,因此需要正确处理线程间的通信,确保数据更新不会引发线程安全问题。本篇将详细介绍如何在WPF中实现同线程绑定以及跨线程绑定。 ...
通过以上讨论,我们可以理解在独立线程中显示WPF窗口的关键技术和注意事项,包括多线程设计、线程安全、Dispatcher的使用以及异步编程模型。理解和掌握这些知识点能帮助开发者创建高效且响应迅速的WPF应用程序。
WPF的线程模型与WinForm略有不同,它使用了`Dispatcher`对象。每个UI线程都有一个`Dispatcher`,负责处理该线程的消息。你可以使用`Dispatcher.Invoke`或`Dispatcher.BeginInvoke`来确保代码在UI线程上执行。例如: ...
综上所述,WPF多线程演示项目可能涵盖了如何在WPF应用中正确使用多线程、避免阻塞UI、利用BackgroundWorker、Task Parallel Library或异步编程模型来实现后台任务,以及处理线程安全和异常等问题。这些知识点对于...
此外,对于复杂的3D模型,性能优化也是需要注意的问题,比如模型的简化、多线程加载和内存管理等。 总的来说,WPF加载OBJ格式3D模型涉及3D图形学、文件解析和WPF编程等多个技术层面。理解这些知识点并熟练运用,...
在Windows Presentation Foundation (WPF)应用程序中,处理大量计算或长时间运行的任务时,为了保持用户界面(UI)的响应性,通常需要使用多线程技术。WPF多线程允许开发者在后台线程上执行耗时操作,避免阻塞主线程...
在WPF中,有两种主要的多线程模型:后台线程(BackgroundWorker)和Dispatcher。后台线程适用于简单的异步操作,而Dispatcher则用于更复杂的UI更新需求。 1. **后台线程(BackgroundWorker)**:BackgroundWorker类...
在Windows Presentation Foundation (WPF) 中,3D图形渲染是一个强大的...通过上述方法,即使在处理一万个模型时,也能确保WPF 3D应用的流畅运行。在实际项目中,应根据具体场景灵活运用这些优化技巧,以实现最佳性能。
3. **后台导入模型**:在C#代码中,使用`Task.Run`或者异步方法在后台线程加载3D模型,避免阻塞UI线程。例如,对于`.obj`格式的模型: ```csharp private async Task LoadModelAsync() { var model = await ...
在给定的“wpf窗体多线程实现生产者消费者模型”中,我们将探讨如何利用C#的线程和信号量来实现这一模式,以及如何通过回调函数在工作线程中更新WPF窗体的UI。 首先,理解生产者消费者模型的基本概念至关重要。生产...
在传统的单线程WPF应用中,如果一个耗时的任务(如网络请求或大量数据处理)在主线程上运行,UI将变得无响应,用户无法与之交互,这被称为“冻结UI”现象。为了避免这种情况,开发者需要学习如何在后台线程上执行...
在Windows Presentation Foundation(WPF)应用开发中,后台任务线程和进度处理是常见的需求,尤其是在执行长时间运行的任务时,为了保持用户界面的响应性,我们需要将这些任务放到单独的线程中执行。`...
1. **全局异常处理**:通过在`App.xaml.cs`的`DispatcherUnhandledException`事件中设置处理器,可以捕获未处理的UI线程异常。这样,即使在代码中没有显式处理的异常也能得到妥善处理,避免程序崩溃。 2. **特定...
异步编程模型,如`Task`和`async/await`关键字,也是C#中处理多线程的常用方式,它们能够简化并发编程,提高程序的响应性和性能。 WPF中,多线程的使用尤其需要注意UI线程与后台线程的交互。因为WPF的UI元素只能在...
每个WPF线程都有一个与之关联的Dispatcher,用于调度UI操作。如果一个线程需要修改UI元素,它必须通过调用Dispatcher的Invoke或BeginInvoke方法,将操作提交到正确的线程执行。这样可以确保所有的UI更新都在正确的...
1. 使用`Dispatcher.Invoke()`或`Dispatcher.BeginInvoke()`:这两个方法会将指定的委托(Delegate)放入UI线程的消息队列,等待主线程处理。`Invoke`是同步的,会阻塞当前线程直到UI操作完成;而`BeginInvoke`是...
双事件处理线程(或称为双线程模型)是一种特定的并发设计模式,常用于GUI应用中。在这种模型中,有两个主要的线程:一个主线程,通常负责用户界面的更新,确保UI的响应性;另一个后台线程,用于处理耗时的任务,如...