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

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

例子:打开图片

同步、异步区别
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. linux安装软件的几种方法----linux下编译安装软件的一般步骤

    linux安装软件的几种方法: 一.rpm包安装方式步骤: 1.找到相应的软件包,比如soft.version.rpm,下载到本机某个目录: 2.打开一个终端,su -成root用户: 3.cd so ...

  2. MQTT 协议学习: 总结 与 各种定义的速查表

    背景 经过几天的学习与实操,对于MQTT(主要针对 v3.1.1版本)的学习告一段落,为了方便日后的查阅 本文链接:<MQTT 协议学习: 总结 与 各种定义的速查表> 章节整理 MQTT ...

  3. jmeter用Stepping Thread Group 递增并发数

    jmeter安装插件Stepping Thread Group 如图所示设置的时候,本以为是每2秒 按 1 2 3 4 递增的,总共请求应该是10个,可是运行后却请求了几十个. 这个是有关线程数是否就 ...

  4. HiBench成长笔记——(11) 分析源码run.sh

    #!/bin/bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor licen ...

  5. helloctf.exe ----攻防世界

    下载附件之后,查壳发现没有壳,运行试试看 很平常的输入代码,然后ida查看一下,开始就发现一个注意的地方,但是还是继续向下看, 结果就是CrackMeJustForFun

  6. R 《回归分析与线性统计模型》page93.6

    rm(list = ls()) #数据处理 library(openxlsx) library(car) library(lmtest) data = read.xlsx("xiti4.xl ...

  7. python多进程编程中常常能用到的几种方法

    python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU资源,在python中大部分情况需要使用多进程.python提供了非常好用的多进程包Multiprocessing,只需要定义 ...

  8. delphi的dbgrid控件点击title排序

    procedure TfrmMain.DBGridEhTitleClick(Column: TColumnEh);var i : integer;begin for i:= 1 to DBGridEh ...

  9. [题解] LuoguP3768 简单的数学题

    Description 传送门 给一个整数\(n\),让你求 \[ \sum\limits_{i=1}^n \sum\limits_{j=1}^n ij\gcd(i,j) \] 对一个大质数\(p\) ...

  10. 如何更改RStudio(或R)中的默认目录

    方法一: Session -> Set Working Directory -> Choose Directory ... or shortcut (Ctrl+Shift+H) 方法二 s ...