wpf同步与异步执行技术

发布于:2021-07-20 21:22:53

Wpf 的同步与异步执行技术
在 WPF, 创建的线程 DispatcherObject 才能访问该对象。 例如,从主 UI 线程派生的后台线程不能更新的内容 Button UI 线程上创建。 为了使后台线程访问 的内容属性的 Button, ,后台线程必须将工作委托给 Dispatcher 与 UI 线程关联。 这通过使用实现 Invoke 或 BeginInvoke。 Invoke 是同步 和 BeginInvoke 是异步的。 该操作将添加到事件队列的 Dispatcher 指定 DispatcherPriority。 1、同步 Invoke 是同步操作;因此,控件不会返回对调用对象直到回调返回后。

同步执行

this.Dispatcher.Invoke(new Action(() => { //Do Something //更新 UI 操作 }));

例如:

如果是窗体本身可使用类似如下的代码:

1 this.lblState.Dispatcher.Invoke(new Action(delegate 2 { 3 this.lblState.Content = "状态:" + this._statusText; 4 }));

那么假如是在一个公共类中弹出一个窗口、播放声音等呢?这里我们可以使用:System.Windows.Application.Current.Dispatcher,如下所示

1 System.Windows.Application.Current.Dispatcher.Invoke(new Action(() => 2 { 3 if (path.EndsWith(".mp3") || path.EndsWith(".wma") || path.EndsWith(".wav")) 4 { 5 _player.Open(new Uri(path)); 6 _player.Play(); 7 } 8 }));

2、异步执行 案例:Wpf 执行异步操作时显示 Gif 动画。

1)在按钮方法中创建并执行线程:

2)定义异步操作 3)在异步操作中异步修改UI中的控件属性。且该操作中不能执行耗时多的操作,以防UI卡顿。 本质是进行两次异步操作。

private void btnRun_Click(object sender, RoutedEventArgs e) { startgif.Visibility = Visibility.Visible; btnClear.IsEnabled = false; btnRun.IsEnabled = false; lblShow.Content += "开始系统检查......\n"; //记录操作时间 lblShow.Content += "in btnRun_Click " + DateTime.Now.ToString() + "\n"; //在新线程中异步执行系统检查 Thread newThread = new Thread(GetSystemCheckResult); newThread.Start();

//创建线程,不带参数 //执行线程

//记录操作时间 lblShow.Content += "out btnRun_Click " + DateTime.Now.ToString() + "\n";

}
1. 2. Thread newThread = new Thread(new ParameterizedThreadStart(GetResult)); newThread.Start(inputNumber); //可传入参数的线程 //inputNumber 为传的参数

1. private void GetResult(object inputNumber) 2. { 3. 4. 5. 6. 7. 8. 9. } });

//使用参数 inputNumber 进行异步操作

double result=CalcSum((Int64)inputNumber); this.Dispatcher.BeginInvoke((Action)delegate()
{ //this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();

this.textBox2.Text = result.ToString();

C# 多线程 用委托实现异步_调用委托的 BeginInvoke 和 EndInvoke 方法
原创 2014 年 12 月 04 日 15:38:18
? ? ? ? ?

标签: 异步 / 线程 / delegate 5173

1.C#中的每一个委托都内置了 BeginInvoke 和 EndInvoke 方法,如果委托的方法列表里只有一个方法,那么这个方法 就可以异步执行(不在当前线程里执行,另开辟一个线程执行)。委托的 BeginInvoke 和 EndInvoke 方法就是为了上 述目的而生的。 2.原始线程发起了一个异步线程,有如下三种执行方式:

方式一:等待一直到完成,即原始线程在发起了异步线程以及做了一些必要处理之后,原始线程就中断并等待异步线程 结束再继续执行。 方式二:轮询,即原始线程定期检查发起的线程是否完成,如果没有则可以继续做一些其它事情。 方式三:回调,即原始线程一直执行,无需等待或检查发起的线程是否完成。在发起的线程执行结束,发起的线程就会 调用用户定义好的回调方法,由这个回调方法在调用 EndInvoke 之前处理异步方法执行得到的结果。 3.一个控制台小程序,使用了上面三种方式,执行结果如下:

4.代码:
[csharp] view plain copy

1. using System; 2. using System.Collections.Generic; 3. using System.Linq; 4. using System.Runtime.Remoting.Messaging; 5. using System.Text; 6. using System.Threading; 7. 8. namespace 用委托实现异步_调用 BeginInvoke 和 EndInvoke 方法 9. { 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. }

delegate long MyDel(int first,int second); //声明委托类型 class Program
{ //声明委托类型的方法

static long Sum(int x,int y)
{ Console.WriteLine(" Thread.Sleep(200); Inside Sum");

return x + y;

22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. } Console.WriteLine("Doing main stuff before"); //方式一:等待异步线程结束,再继续执行主线程 } //定义当异步线程执行结束要执行的回调函数

static void CallWhenDone(IAsyncResult iar)
{ Console.WriteLine(" AsyncResult ar = (AsyncResult)iar; MyDel del = (MyDel)ar.AsyncDelegate; Inside CallWhenDone");

long result = del.EndInvoke(iar);
Console.WriteLine(" The result is {0}.", result);

static void WaitUntilDoneStyle()
{ MyDel del = new MyDel(Sum); Console.WriteLine("Before BeginInvoke"); IAsyncResult iar = del.BeginInvoke(3, 5, null, null); //开始异步调用 Console.WriteLine("After BeginInvoke");

long result = del.EndInvoke(iar); //等待异步线程结束并获取结果
Console.WriteLine("After EndInvoke:{0}", result); Console.WriteLine("Doing main stuff after");

48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73.

//方式二:轮询检查异步线程是否结束,若没结束则执行主线程

static void LunXunPollingStyle()
{ MyDel del = new MyDel(Sum); Console.WriteLine("Before BeginInvoke"); IAsyncResult iar = del.BeginInvoke(3, 5, null, null); //开始异步调用 Console.WriteLine("After BeginInvoke");

while (!iar.IsCompleted)
{ Console.WriteLine("Not Done.Doing main stuff"); //继续处理主线程事情

for (long i = 0; i < 10000000; i++)
; } Console.WriteLine("Done");

long result = del.EndInvoke(iar); //调用 EndInvoke 来获取结果并进行清理
Console.WriteLine("Result: {0}", result); } //方式三:回调方式,当异步线程结束,系统调用用户自定义的方法来处理结果(包括调用委托的 EndInvoke 方法)

static void CallBackStyle()
{ MyDel del = new MyDel(Sum); Console.WriteLine("Before BeginInvoke"); IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);

74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. ----"); 95. 96. 97. 98. } }

Console.WriteLine("After BeginInvoke"); Console.WriteLine("Doing more work in main."); Thread.Sleep(500); Console.WriteLine("Done with Main. Exiting.");

static void Main(string[] args)
{ //方式一:等待异步线程结束,再继续执行主线程 Console.WriteLine(); Console.WriteLine("--------方式一:等待异步线程结束,再继续执行主线程--------"); WaitUntilDoneStyle(); //方式二:轮询检查异步线程是否结束,若没结束则执行主线程 Console.WriteLine(); Console.WriteLine("--------方式二:轮询检查异步线程是否结束,若没结束则执行主线程--------"); LunXunPollingStyle(); //方式三:回调方式,当异步线程结束,系统调用用户自定义的方法来处理结果(包括调用委托的 EndInvoke 方法) Console.WriteLine(); Console.WriteLine("--------方式三:回调方式,当异步线程结束,系统调用用户自定义的方法来处理结果(包括调用委托的 EndInvoke 方法)---CallBackStyle();

99. 100. }

}

Dispatcher.BeginInvoke()方法使用不当导致 UI 界面卡死的原因分析
原创 2013 年 09 月 15 日 18:06:59
? ? ? ? ? ? ?

标签: thread / c# / c#4.0 / 异步 / delegate 17372

前段时间,公司同事开发了一个小工具,在工具执行过程中,UI 界面一直处于卡死状态。 通过阅读代码发现,主要是由于 Dispatcher.BeginInvoke()方法使用不当导致的。 本文将通过一个 WPF 模拟程序来演示一下界面卡死的现象,并通过修改代码来解决界面卡死的问题。 希望通过对本文的学*,大家能对 Dispatcher.BeginInvoke()方法有一个新的认识。

文章开篇直接给出界面卡死的示例代码。 示例 WPF 程序,用来计算 1~n 的和值,这里的 n 可以是 1 亿~25 亿之间的某个值,通过界面录入,结果显示在 n 输入框后面的文本框 中,既然是 WPF 程序,代码包含 xaml 及 cs 代码两部分,本文一并给出。 以下为 cs 代码:
[csharp] view plain copy

1. using System; 2. using System.Windows; 3. using System.Threading; 4. 5. namespace DispatcherExample 6. { 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. } /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary>

public partial class MainWindow : Window
{

public MainWindow()
{ InitializeComponent();

17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42.

private void button1_Click(object sender, RoutedEventArgs e)
{ Int64 inputNumber;

if (!Int64.TryParse(this.textBox1.Text, out inputNumber))
{ MessageBox.Show("请输入 1 亿-10 亿皑间的整型数据!");

return;
}

if (inputNumber > 2500000000 || inputNumber<100000000)
{ MessageBox.Show("请输入 1 亿-10 亿间的整型数据!");

return;
} Thread newThread = new Thread(new ParameterizedThreadStart(GetResult)); newThread.Start(inputNumber); }

private void GetResult(object inputNumber)
{

this.Dispatcher.BeginInvoke((Action)delegate()
{

this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();
}); }

private double CalcSum(Int64 inputNumber)

43. 44. 45. 46. 47. 48. 49. 50. 51. 52. } }

{

double sum=0; for (int i = 0; i < inputNumber; i++)
{ sum +=i; }

return sum;
}

以下为 xaml 代码:
[html] view plain copy

1. <Window x:Class="DispatcherExample.MainWindow" 2. 3. 4. 5. 6. 7. 8. 9. 10. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="求(和)你亿万次~~" Height="350" Width="525" ResizeMode="NoResize"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="252*" /> <ColumnDefinition Width="251*" /> </Grid.ColumnDefinitions> <Button Content="计算和值" Height="23" HorizontalAlignment="Left" Margin="213,168,0,0" Name="button1" VerticalAlignment="Top" W idth="75" Click="button1_Click" Grid.ColumnSpan="2" />

11. 12.

<Label Content="输入 1 亿-25 亿间的数字:" Height="28" HorizontalAlignment="Left" Margin="36,93,0,0" Name="label1" VerticalAlignmen t="Top" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="158,96,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Grid.C olumnSpan="2" /> <TextBox Height="23" HorizontalAlignment="Left" Margin="35,96,0,0" Name="textBox2" VerticalAlignment="Top" Width="177" Text="结 果看这里..." Grid.Column="1" /> </Grid>

13. 14.

15. </Window>

执行程序,界面如下:

输入 2500000000,点击“计算和值”按钮,程序开始计算和值,界面卡死,无法再操作该程序(如移动位置或重新输入等)。 分析代码, 发现问题应该出在下面的代码中, 因为该部分代码中存在调用 UI 主线程的操作, 此种操作不当往往会导致界面卡死的现象。
[csharp] view plain copy

1. private void GetResult(object inputNumber) 2. {

3. 4. 5. 6. 7. }

this.Dispatcher.BeginInvoke((Action)delegate()
{

this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();
});

那么,问题到底出在哪里呢? 要想弄清楚这点,还得了解一下 Dispatcher.BeginInvoke()方法。 MSDN 上对 Dispatcher.BeginInvoke 方法的解释如下: Dispatcher.BeginInvoke 方法 (Action) 在与 Dispatcher 关联的线程上异步执行指定的委托。 那么本实例中,与 Dispatcher 关联的线程是什么呢? 要想弄清楚这点很简单。只要知道 this.Dispatcher.BeginInvoke()中的 this 指的是什么就可以了。在 Visual studio 中将鼠标至于 th is 上,发现 this 指的是当前的窗体类(如下图),即程序的主线程。

到这,我们应该知道问题出在哪里了。 原因是:在 GetResult()方法中,将求和的操作交由主线程来完成,当计算未完成时,界面自然会被卡死。 通过与同事交谈了解到,他其实想要的是:新开一个线程来完成自己预想的运算(类似于示例程序中的求和运算),在结果出来后再 调用主线程显示结果。 这样界面就不会出现卡死现象,但是上面的代码并没有达到预想结果。 原因前面已经交代了,因为这段代码将求和的计算仍然丢给了主线程,尽管新开了线程,但是新开线程并不进行求和运算,可以说是 绕了一圈又回来了。 主线程开新线程,新线程又调用主线程。这有点像工作中的踢皮球,我给你一件事,你说不会,又踢回给我。

找到原因再修改就简单了,修改后的代码如下:
[csharp] view plain copy

1. private void GetResult(object inputNumber) 2. { 3. 4. 5. 6. 7. 8. 9. } });

double result=CalcSum((Int64)inputNumber); this.Dispatcher.BeginInvoke((Action)delegate()
{ //this.textBox2.Text = CalcSum((Int64)inputNumber).ToString();

this.textBox2.Text = result.ToString();

至于为什么要这样修改,我想:你懂的。 再次执行程序,输入 2500000000,求和,界面不再存在卡死现象。


相关推荐

最新更新

猜你喜欢