在执行较为耗时的处理时,很容易出现用户界面“卡顿”现象,用异步编程模型,将耗时处理的代码放到另一个线程上执行,不会阻止用户界面线程的继续执行,应用程序 就不再出现“卡顿”现象。

本例子提供同步加载和异步加载两种实现。

例子:打开图片

同步、异步区别
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.IO; namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void AddItemToListView(string filename, int width, int height, float dpix, float dpiy)
{
ListViewItem item = new ListViewItem();
item.Text = filename;
item.SubItems.Add(width.ToString());
item.SubItems.Add(height.ToString());
item.SubItems.Add(dpix.ToString("N1"));
item.SubItems.Add(dpiy.ToString("N1"));
this.listView1.Items.Add(item);
} private void btnSyn_Click(object sender, EventArgs e)//同步
{
if (this.folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
this.listView1.Items.Clear();
this.listView1.BeginUpdate(); //开始更新
// 取得目录路径
string dir = this.folderBrowserDialog1.SelectedPath;
// 搜索目录下的所有Jpg图片
string[] jpgFiles = null;
try
{
jpgFiles = Directory.GetFiles(dir, "*.jpg", SearchOption.AllDirectories);
}
catch { }
if (jpgFiles != null)
{
foreach (string file in jpgFiles)
{
// 从文件中加载图像
string fileName = Path.GetFileName(file);
int width = , height = ;
float dpiX = 0f, dpiY = 0f;
// 读取图像数据
using (Bitmap bmp = (Bitmap)Bitmap.FromFile(file))
{
width = bmp.Width;
height = bmp.Height;
dpiX = bmp.HorizontalResolution;
dpiY = bmp.VerticalResolution;
}
// 向ListView中添加项
AddItemToListView(fileName, width, height, dpiX, dpiY);
}
}
this.listView1.EndUpdate(); //结束更新
}
} private void btnAsync_Click(object sender, EventArgs e)//异步
{
if (this.folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
// 取得用户选择的路径
string dir = this.folderBrowserDialog1.SelectedPath;
// 清空ListView中的项
this.listView1.Items.Clear();
this.listView1.BeginUpdate();
this.btnAsync.Enabled = btnSyn.Enabled = false;
Task.Run(() =>
{
// 获取目录下的所有Jpg文件
string[] files = Directory.GetFiles(dir, "*.jpg", SearchOption.AllDirectories);
foreach (string imgFile in files)
{
string imageFileName; //文件名
int width, height; //宽度和高度
float dpiX, dpiY; //水平和垂直分辨率 imageFileName = Path.GetFileName(imgFile);
// 加载图像
using (Bitmap bmp = (Bitmap)Bitmap.FromFile(imgFile))
{
width = bmp.Width;
height = bmp.Height;
dpiX = bmp.HorizontalResolution;
dpiY = bmp.VerticalResolution;
}
// 向ListView添加项
listView1.BeginInvoke((Action<string, int, int, float, float>)AddItemToListView, imageFileName, width, height, dpiX, dpiY);
} // 完成更新
listView1.BeginInvoke((Action)delegate()
{
listView1.EndUpdate();
btnSyn.Enabled = btnAsync.Enabled = true;
});
});
}
}
}
}

TASK

Task为.NET提供了基于任务的异步模式,它不是线程,它运行在线程池的线程上。

创建 Task 
创建Task有两种方式,一种是使用构造函数创建,另一种是使用 Task.Factory.StartNew 进行创建。如下代码所示

一.使用构造函数创建Task
Task t1 = new Task(MyMethod); 
二.使用Task.Factory.StartNew 进行创建Task
Task t1 = Task.Factory.StartNew(MyMethod);
其实方法一和方法二这两种方式都是一样的,Task.Factory 是对Task进行管理,调度管理这一类的。好学的伙伴们,可以深入研究。这不是本文的范畴,也许会在后面的文章细说。

Task 类还提供了构造函数对任务进行初始化,但的未计划的执行。 出于性能原因, Task.Run 或 TaskFactory.StartNew(工厂创建) 方法是用于创建和计划计算的任务的首选的机制,但对于创建和计划必须分开的方案,您可以使用的构造函数(new一个出来),然后调用 Task.Start 方法来计划任务,以在稍后某个时间执行。

 //第一种创建方式,直接实例化:必须手动去Start
var task1 = new Task(() =>
{
//TODO you code
});
task1.Start(); //第二种创建方式,工厂创建,直接执行
var task2 = Task.Factory.StartNew(() =>
{
//TODO you code
});

在实际编程中,我们用的较多的是Task、Task.Factory.StarNew、Task.Run,接下来简单的表述下我的理解。

新手浅谈C#Task异步编程

Task与Task<TResult>类

Task类和Task<TResult>类,后者是前者的泛型版本。TResult类型为Task所调用方法的返回值。

主要区别在于Task构造函数接受的参数是Action委托,而Task<TResult>接受的是Func<TResult>委托。 

  1. Task(Action)
  2. Task<TResult>(Func<TResult>)

启动一个任务

  1. static void Main(string[] args)
  2. {
  3. Task Task1 = new Task(() => Console.WriteLine("Task1"));
  4. Task1.Start();
  5. Console.ReadLine();
  6. }

 

通过实例化一个Task对象,然后Start,这种方式中规中矩,但是实践中,通常采用更方便快捷的方式

Task.Run(() => Console.WriteLine("Foo"));

这种方式直接运行了Task,不像上面的方法还需要调用Start();

Task.Run方法是Task类中的静态方法,接受的参数是委托。返回值是为该Task对象。

Task.Run(Action)

Task.Run<TResult>(Func<Task<TResult>>)

下面例子计算阶乘,并返回结果

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
uint baseNum = ;
if (!uint.TryParse(textBox1.Text, out baseNum))
{
MessageBox.Show("请输入一个整数作为基数。");
return;
} // 更新进度条的组件
IProgress<int> progress = new Progress<int>((p) => progressBar1.Value = p);
// 声明后台任务
Task<long> task = new Task<long>(() =>
{
long res = 0L;
for (int n = ; n <= baseNum; n++)
{
// 累加
res += n;
// 计算进度
double pr = Convert.ToDouble(n) / Convert.ToDouble(baseNum) * 100d;
// 报告进度
progress.Report(Convert.ToInt32(pr));
}
return res;
});
textBox2.Text = "正在计算……";
// 启动任务
task.Start();
// 等待任务完成
button1.Enabled = false;
while (!task.Wait())
{
// 在等待过程中允许程序处理其他消息
Application.DoEvents();
}
button1.Enabled = true;
// 获取计算结果
textBox2.Text = "计算结果:" + task.Result.ToString();
}
}
}

因为要返回最终计算结果,所以本例子使用的是Task<TResult>类,在实例 化的时候 需要提供一个委托实例,该委托定义异步任务需要执行哪些操作。在调用start方法后,任务就被启动了,随后可以调用wait方法来等待任务完成。

BeginInvoke方法

C# 委托的三种调用示例(同步调用、异步调用、异步回调)

由于线程安全和保护用户界面完整性的考虑,一般来说,不能跨线程去修改用户界面,因此,需要调用BeginInvoke方法并通过一个委托在用户界面所在的线程上去更新用户界面。

传递给Thread构造函数的委托有两种:一种表示不带参数方法的委托;另一种表示 带一个object类型 参数方法的委托。

BeginInvoke与Invoke的区别?????

Thread类 多线程

C#thread线程类

Thread类位于System.Threading下,如果 要用,需引用。

thread.IsBackground=true;可以设置为后台线程

thread.Start();开始执行线程。

Thread.Sleep方法是静态方法,表示让当前线暂停一段时间后,再往下执行,通常以毫秒为计算单位。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms; namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
Thread newThread = new Thread(DoWork);
// 禁用按钮
button1.Enabled = false;
// 启动新线程
newThread.Start();
} /// <summary>
/// 该方法将在新线程上执行
/// </summary>
private void DoWork()
{
int n = ;
while (n < )
{
n++;
Thread.Sleep();
// 更新进度条
this.BeginInvoke(new Action(() =>
{
this.progressBar1.Value = n;
}));
}
this.BeginInvoke(new Action(delegate()
{
// 恢复按钮的可用状态
button1.Enabled = true;
// 恢复进度条的值为0
progressBar1.Value = ;
// 显示提示信息
MessageBox.Show("操作完成。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
}
}
}

线程锁

由于线程是抢占式使用处理器的时间片,当多个线程同时访问某个资源时就会出现不一致的问题。

为解决一个问题,c#语言中提供 了lock关键字,该关键字可以对多个线程都 要访问的资源 进行锁定,哪个线程首先占有资源就把该资源锁定,其他想要访问该资源的线程只能等待改线程访问完成并解锁后才能访问资源,也就说,同一时刻只允许一个线程访问资源 。

将要锁定的代码放到紧跟lock关键字的语句块中,lock关键字要使用一个引用类型的实例来完成锁定。

lock(obj)

{

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} private void OnStart(object sender, RoutedEventArgs e)
{
TicketNum = 10;
txtDisplay.Clear();
// 创建15个线程并启动
Thread[] ths = new Thread[15];
for (int x = 0; x < 15; x++)
{
ths[x] = new Thread(Sell);
}
foreach (Thread t in ths)
{
t.Start();
}
} // 表示所剩飞机票的数量
private static int TicketNum = 10; // 用于加锁时使用的对象
private object lockObject = new object(); private void Sell()
{
lock (lockObject) //锁定
{
if (TicketNum > 0)
{
Thread.Sleep(300);
// 数量减1
TicketNum--;
// 显示剩余的票数
string msg = string.Format("还剩余{0}张机票。\n", TicketNum.ToString());
this.Dispatcher.BeginInvoke((Action)(delegate()//WPF应用程序中也不允许跨线程直接访问用户界面元素,必须通过Dispatcher.BeginInvoke方法来访问用户界面元素。
{
txtDisplay.AppendText(msg);
}));
}
}
}
}
}

  

通过委托执行异步操作

使用IProgress实现异步编程的进程通知

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Numerics; namespace MyApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
} private void btnStart_Click(object sender, EventArgs e)
{
int baseNum = default(int);
if (!int.TryParse(txtBaseNum.Text, out baseNum))
{
MessageBox.Show("请输入一个整数。");
return;
}
txtResult.Clear();
// 用于报告进度的对象
IProgress<int> progressReporter = new Progress<int>((p) =>
{
this.progressBar1.Value = p;
});
// 计算阶乘的委托实例
Func<int, BigInteger> ComputeAction = (bsNum) =>
{
BigInteger bi = new BigInteger();
for (int i = ; i <= bsNum; i++)
{
bi *= i; //相乘
// 计算当前进度
double ps = Convert.ToDouble(i) / Convert.ToDouble(bsNum) * 100d;// 进度条值
progressReporter.Report(Convert.ToInt32(ps));
}
return bi;
}; // 开始调用
btnStart.Enabled = false;
ComputeAction.BeginInvoke(baseNum, new AsyncCallback(FinishCallback), ComputeAction);
} private void FinishCallback(IAsyncResult ar)
{
// 取出委托变量
Func<int, BigInteger> action = (Func<int, BigInteger>)ar.AsyncState;
// 得到计算结果
BigInteger res = action.EndInvoke(ar);
this.BeginInvoke(new Action(() =>
{
btnStart.Enabled = true;
// 显示计算结果
txtResult.Text = string.Format("计算结果:{0}", res);
}));
}
}
}

Progress<T> 实现了IProgress<T>接口,可以通过 Report方法报告与操作进度相关的数据,该对象在进度更新后允许代码直接操作用户界面。

Func<int,BigInteger>委托表示带有一个int类型的参数,并且返回BigInteger实现的方法。为什么以要用BigInteger结构来保存计算结果呢?因为阶乘的计算结果可能 很大,超出long值 的有效范围也是正常现象。

C# 并行任务——Parallel类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading; namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} private void OnClick(object sender, RoutedEventArgs e)
{
int num1 = default(int);
if (!int.TryParse(txtInput1.Text, out num1))
{
MessageBox.Show("请输入第一个基数。");
return;
}
int num2 = default(int);
if (!int.TryParse(txtInput2.Text, out num2))
{
MessageBox.Show("请输入第二个基数。");
return;
}
int num3 = default(int);
if (!int.TryParse(txtInput3.Text, out num3))
{
MessageBox.Show("请输入第三个基数。");
return;
} // 声明用于并行操作的委托实例
Action act1 = () =>
{
int sum = ;
for (int x = ; x <= num1; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run1.Text = sum.ToString()));
};
Action act2 = () =>
{
int sum = ;
for (int x = ; x <= num2; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run2.Text = sum.ToString()));
};
Action act3 = () =>
{
int sum = ;
for (int x = ; x <= num3; x++)
{
sum += x;
}
// 显示结果
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => run3.Text = sum.ToString()));
}; // 开始执行并行任务
Parallel.Invoke(act1, act2, act3);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO; namespace MyApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} private void OnClick(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtDir.Text))
{
MessageBox.Show("请输入有效的目录名。");
return;
}
// 如果目录不存在,先创建目录
if (!Directory.Exists(txtDir.Text))
{
Directory.CreateDirectory(txtDir.Text);
} int fileNum = ;
if (!int.TryParse(txtFileNum.Text, out fileNum))
{
MessageBox.Show("请输入文件数量。"); return;
}
long fileSize = 0L;
if (long.TryParse(txtSize.Text, out fileSize) == false)
{
MessageBox.Show("请输入正确的文件大小。");
return;
}
// 产生名件名列表
List<string> fileNames = new List<string>();
for (int n = ; n < fileNum; n++)
{
// 文件名
string _filename = "file_" + (n + ).ToString();
// 目录的绝对路径
string _dirpath = System.IO.Path.GetFullPath(txtDir.Text);
// 组建新文件的全路径
string fullPath = System.IO.Path.Combine(_dirpath, _filename);
fileNames.Add(fullPath);
}
txtDisplay.Clear();
// 用于产生随机字节
Random rand = new Random(); // 用于执行任务的委托实例
Action<string> act = (file) =>
{
FileInfo fileInfo = new FileInfo(file);
// 如果文件存在,则删除
if (fileInfo.Exists)
fileInfo.Delete();
// 将数据写入文件
byte[] buffer = new byte[fileSize];
rand.NextBytes(buffer);
using (FileStream fs = fileInfo.Create())
{
BinaryWriter sw = new BinaryWriter(fs);
sw.Write(buffer);
sw.Close();
sw.Dispose();
}
// 显示结果
string msg = string.Format("文件{0}已成功写入。\n", fileInfo.Name);
this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => txtDisplay.AppendText(msg)));
}; // 启动循环任务
Parallel.ForEach<string>(fileNames, act);
}
}
}

c# action<> func<> 这2个委托怎么用和理解

c# 异步和同步 多线程的更多相关文章

  1. Linux 多线程 - 线程异步与同步机制

    Linux 多线程 - 线程异步与同步机制 I. 同步机制 线程间的同步机制主要包括三个: 互斥锁:以排他的方式,防止共享资源被并发访问:互斥锁为二元变量, 状态为0-开锁.1-上锁;开锁必须由上锁的 ...

  2. C# 多线程、异步、同步之间的联系与区别

    C# 多线程.异步.同步之间的联系与区别 假设这样一个例子: 我想炒五样菜,但是只有两个炉子可以用,只能同时炒两样. 炉子就是线程,那同步跟异步怎么解释比较好? 同时炒是不是算异步? 如果是的话,那什 ...

  3. C#中的异步和同步

    同步 同步(英语:Synchronization [ˌsɪŋkrənaɪ'zeɪʃn]),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...

  4. MacOS和iOS开发中异步调用与多线程的区别

    很多童鞋可能对Apple开发中的异步调用和多线程的区别不是太清楚,这里本猫将用一些简单的示例来展示一下它们到底直观上有神马不同. 首先异步调用可以在同一个线程中,也可以在多个不同的线程中.每个线程都有 ...

  5. net 异步与同步

    一.摘论 为什么不是摘要呢?其实这个是我个人的想法,其实很多人在谈论异步与同步的时候都忽略了,同步异步不是软件的原理,其本身是计算机的原理及概念,这里就不过多的阐述计算机原理了.在学习同步与异步之前, ...

  6. java 异步调用与多线程

    异步与多线程的区别 一.异步和多线程有什么区别?其实,异步是目的,而多 线程是实现这个目的的方法.异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作 就没有必要异步了),可以继续自顾 ...

  7. 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步

    深入理解MVC   MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...

  8. 正确理解这四个重要且容易混乱的知识点:异步,同步,阻塞,非阻塞,5种IO模型

    本文讨论的背景是Linux环境下的network IO,同步IO和异步IO,阻塞IO和非阻塞IO分别是什么 概念说明 在进行解释之前,首先要说明几个概念: - 用户空间和内核空间 - 进程切换 - 进 ...

  9. Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程

    Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...

随机推荐

  1. P10891089 狼人杀-简单版

    1089 狼人杀-简单版 (20分)   以下文字摘自<灵机一动·好玩的数学>:“狼人杀”游戏分为狼人.好人两大阵营.在一局“狼人杀”游戏中,1 号玩家说:“2 号是狼人”,2 号玩家说: ...

  2. 解决使用还原卡的PC在2个月后要重新加入域的问题

    客户端正确操作: 1. 启动注册表编辑器. 要这样做, 请依次单击 开始 . 运行 , 类型 regedit 在 打开, 框, 然后单击 确定 . 2. 找到并单击以下注册表子项: HKEY_LOCA ...

  3. 杭电oj1860:统计字符(字符串hash / 水题)

    统计字符 题目链接 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem D ...

  4. java核心-多线程(4)-线程类基础知识

    1.并发 <1>使用并发的一个重要原因是提高执行效率.由于I/O等情况阻塞,单个任务并不能充分利用CPU时间.所以在单处理器的机器上也应该使用并发. <2>为了实现并发,操作系 ...

  5. centos7下安装ansible

    由于centos7预装了python,因此我们可以跳过python的安装环节(记得关闭防火墙) [root@model ~]# [root@model ~]# python --version Pyt ...

  6. leetcode617 Merge Two Binary Trees

    """ Given two binary trees and imagine that when you put one of them to cover the oth ...

  7. ActiveMQ的安装与配置详情

    (1)ActiveMQ的简介 MQ: (message queue) ,消息队列,也就是用来处理消息的,(处理JMS的).主要用于大型企业内部或与企业之间的传递数据信息. ActiveMQ 是Apac ...

  8. 十、Vue:Vuex实现data(){}内数据多个组件间共享

    一.概述 官方文档:https://vuex.vuejs.org/zh/installation.html 1.1vuex有什么用 Vuex:实现data(){}内数据多个组件间共享一种解决方案(类似 ...

  9. uniapp 小程序实现自定义底部导航栏(tarbar)

    在小程序开发中,默认底部导航栏很难满足实际需求,好在官方给出了自定义形式,效果如下: 话不多说,直接上代码 1.组件 custom-tarbar.vue文件 <template> < ...

  10. windows下移植别人配置好的python环境

    一般来说,我们在windows下配置python环境的时候可能会比较推荐用anaconda,那么有一个比较方便的anaconda环境移植方法,也就是说,如果我已经在windows上安装好了anacon ...