c# 异步和同步 多线程
在执行较为耗时的处理时,很容易出现用户界面“卡顿”现象,用异步编程模型,将耗时处理的代码放到另一个线程上执行,不会阻止用户界面线程的继续执行,应用程序 就不再出现“卡顿”现象。
本例子提供同步加载和异步加载两种实现。
例子:打开图片
同步、异步区别
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>委托。
- Task(Action)
- Task<TResult>(Func<TResult>)
启动一个任务
- static void Main(string[] args)
- {
- Task Task1 = new Task(() => Console.WriteLine("Task1"));
- Task1.Start();
- Console.ReadLine();
- }
通过实例化一个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值 的有效范围也是正常现象。
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# 异步和同步 多线程的更多相关文章
- Linux 多线程 - 线程异步与同步机制
Linux 多线程 - 线程异步与同步机制 I. 同步机制 线程间的同步机制主要包括三个: 互斥锁:以排他的方式,防止共享资源被并发访问:互斥锁为二元变量, 状态为0-开锁.1-上锁;开锁必须由上锁的 ...
- C# 多线程、异步、同步之间的联系与区别
C# 多线程.异步.同步之间的联系与区别 假设这样一个例子: 我想炒五样菜,但是只有两个炉子可以用,只能同时炒两样. 炉子就是线程,那同步跟异步怎么解释比较好? 同时炒是不是算异步? 如果是的话,那什 ...
- C#中的异步和同步
同步 同步(英语:Synchronization [ˌsɪŋkrənaɪ'zeɪʃn]),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...
- MacOS和iOS开发中异步调用与多线程的区别
很多童鞋可能对Apple开发中的异步调用和多线程的区别不是太清楚,这里本猫将用一些简单的示例来展示一下它们到底直观上有神马不同. 首先异步调用可以在同一个线程中,也可以在多个不同的线程中.每个线程都有 ...
- net 异步与同步
一.摘论 为什么不是摘要呢?其实这个是我个人的想法,其实很多人在谈论异步与同步的时候都忽略了,同步异步不是软件的原理,其本身是计算机的原理及概念,这里就不过多的阐述计算机原理了.在学习同步与异步之前, ...
- java 异步调用与多线程
异步与多线程的区别 一.异步和多线程有什么区别?其实,异步是目的,而多 线程是实现这个目的的方法.异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作 就没有必要异步了),可以继续自顾 ...
- 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步
深入理解MVC MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...
- 正确理解这四个重要且容易混乱的知识点:异步,同步,阻塞,非阻塞,5种IO模型
本文讨论的背景是Linux环境下的network IO,同步IO和异步IO,阻塞IO和非阻塞IO分别是什么 概念说明 在进行解释之前,首先要说明几个概念: - 用户空间和内核空间 - 进程切换 - 进 ...
- Python并发编程06 /阻塞、异步调用/同步调用、异步回调函数、线程queue、事件event、协程
Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件event.协程 目录 Python并发编程06 /阻塞.异步调用/同步调用.异步回调函数.线程queue.事件 ...
随机推荐
- Deepctr框架代码阅读
DeepCtr是一个简易的CTR模型框架,集成了深度学习流行的所有模型,适合学推荐系统模型的人参考. 我在参加比赛中用到了这个框架,但是效果一般,为了搞清楚原因从算法和框架两方面入手.在读代码的过程中 ...
- java.io.FileNotFoundException: rmi_keystore.jks (No such file or directory)(转)
Caused by: java.io.FileNotFoundException: rmi_keystore.jks (没有那个文件或目录) 解决方法:修改jmeter.properites: ser ...
- 链表题目汇总(python3)
1.从头到尾打印链表 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. # -*- coding:utf-8 -*- class ListNode: def __init__(self ...
- JS写一个列表跑马灯效果--基于touchslide.js
先放上效果图: 类似于这样的,在列表中循环添加背景样式的跑马灯效果. 准备引入JS插件: <script type="text/javascript" src="x ...
- 008.Delphi插件之QPlugins,服务的两种调用方法
这个QPlugins自带的DEMO,大概的意思就是,创建2个服务类,程序启动的时候注册这2个服务类.点击不同的按钮,使用不同的方法来调用这个服务. 效果界面如下 unit Frm_Main; inte ...
- java注解——内置注解和四种元注解
java内置注解: @Override(重写方法):被用于标注方法,用于说明所标注的方法是重写父类的方法 @Deprecated(过时方法):用于说明所标注元素,因存在安全问题或有更好选择而不鼓励使用 ...
- Netty的出现
原生NIO存在的问题 NIO的类库和API复杂, 使用麻烦: 需要熟练掌握Selector.ServerSocketChannel.SocketChannel.ByteBuffer 等. 需要具备其他 ...
- redisTemplate注入为空
springboot2.*集成redis时,redis工具类中的redisTemplate注入后总是为空. 问题代码还原: 1.工具类定义成静态工具类,@Resource注入redisTemplate ...
- CANmonitor我自己编写的程序
这个版本的程序, 上位机可以对电机的转速进行在线的设定,同时上位机接受电机控制器上报的母线电压,电机温度,控制器温度等. 在调试的过程中我遇见了一个问题,电机的转速的采样 . 根据协议:电机的转速为1 ...
- 文件上传报错java.io.FileNotFoundException拒绝访问
局部代码如下: File tempFile = new File("G:/tempfileDir"+"/"+fileName); if(!tempFile.ex ...