小标
2018-11-14
来源 :
阅读 1264
评论 0
摘要:本文主要向大家介绍了C#编程之C# 线程,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。
本文主要向大家介绍了C#编程之C# 线程,通过具体的内容向大家展示,希望对大家学习C#编程有所帮助。
C# 线程
委托方式异步
启动方式
我们先声明一个方法:
private void doSomeThing(string name)
{
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行doSomeThing({name}) ……");
Thread.Sleep(2000);
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行doSomeThing({name}) ……");
}
再通过BeginInvoke()执行一个委托:
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行Show()……");
Action
action.BeginInvoke("Oliver", null, null);
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行Show()……");
输出如下:
线程:1 开始执行Show()……
线程:1 结束执行Show()……
线程:3 开始执行doSomeThing(Oliver) ……
线程:3 结束执行doSomeThing(Oliver) ……
判断异步线程执行完毕
我们通过上面的例子就很简单的执行了一个异步线程。但是我们如何判断异步线程执行完毕呢?
1、使用回掉函数
我们在使用BeginInvoke()方法时,它有三个参数,除了我们委托定义的一个参数外,它还有2个参数。
倒数第二个参数,就是异步调用执行完毕之后的回掉函数;最后一个参数是一个Object类型的,我们可以将这个参数传递到回调函数中。
回调函数它也有一个参数是IAsyncResult,这个参数经过我们比对和BeginInvoke()的返回值是一个对象。
通过下面的代码我们实现了在异步线程结束之后,我们调用了我们想调用的方法。
代码如下:
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行Show()……");
Action
IAsyncResult asyncResult = null;
AsyncCallback callback = (ar) =>
{
Console.WriteLine(object.ReferenceEquals(ar, asyncResult));//结果为true,说明两个对象是同一个对象
Console.WriteLine($"ar.AsyncState:{ar.AsyncState}");
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId}异步线程执行完毕了……");
};
asyncResult = action.BeginInvoke("Oliver", callback, "HI");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行Show()……");
输出如下:
线程:1 开始执行Show()……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
线程:1 结束执行Show()……
线程:3 开始执行doSomeThing(Oliver) ……
线程:3 结束执行doSomeThing(Oliver) ……
ar.AsyncState:HI
线程:3异步线程执行完毕了……
2、使用asyncResult.IsCompleted
asyncResult.IsCompleted 表示异步操作是否已完成
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行Show()……");
Action
IAsyncResult asyncResult = action.BeginInvoke("Oliver", null, null);
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
while (!asyncResult.IsCompleted)//判断线程是否执行完毕
{
Thread.Sleep(200);
}
//异步线程执行完毕才会执行下面代码,但是程序会处于假死状态
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行Show()……");
输出:
线程:1 开始执行Show()……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
线程:3 开始执行doSomeThing(Oliver) ……
线程:3 结束执行doSomeThing(Oliver) ……
线程:1 结束执行Show()……
3、使用EndInvoke()
当调用EndInvoke() 方法后,会阻塞当前线程。
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行Show()……");
Action
IAsyncResult asyncResult = action.BeginInvoke("Oliver", null, null);
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
action.EndInvoke(asyncResult);
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行Show()……");
输出:
线程:1 开始执行Show()……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
线程:3 开始执行doSomeThing(Oliver) ……
线程:3 结束执行doSomeThing(Oliver) ……
线程:1 结束执行Show()……
4、使用WaitOne
使用asyncResult.AsyncWaitHandle.WaitOne() 等待当前线程执行完毕
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 开始执行Show()……");
Action
IAsyncResult asyncResult = action.BeginInvoke("Oliver", null, null);
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
Console.WriteLine("……做一些其它的事儿……");
asyncResult.AsyncWaitHandle.WaitOne();//等待任务的完成
//asyncResult.AsyncWaitHandle.WaitOne(-1);//等待任务的完成
//asyncResult.AsyncWaitHandle.WaitOne(1000);//等待;但是最多等待1000ms
Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 结束执行Show()……");
输出:
线程:1 开始执行Show()……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
……做一些其它的事儿……
线程:3 开始执行doSomeThing(Oliver) ……
线程:3 结束执行doSomeThing(Oliver) ……
线程:1 结束执行Show()……
Thread 线程
.netFrameWork 1.0 时出现的
使用方式
Thread 对象创建时,需要传递一个没有参数,没有返回值的委托进去。
Console.WriteLine("show 开始");
Thread thread = new Thread(() =>
{
Console.WriteLine("线程开始了....");
});
thread.Start();
Console.WriteLine("show 结束");
输出:
show 开始
show 结束
线程开始了....
其它
thread.Suspend();//线程挂起
thread.Resume();//唤醒线程
thread.Abort();//销毁,方式是抛异常 也不建议 不一定及时/有些动作发出收不回来
Thread.ResetAbort();//取消Abort异常
thread.Join();//当前线程等待thread完成
thread.Join(500);//最多等500
thread.IsBackground = true;//指定后台线程:随着进程退出。默认是前台线程,启动之后一定要完成任务的,阻止进程退出
thread.Priority = ThreadPriority.Highest;//线程优先级。CPU会优先执行 Highest 不代表说Highest就最先
ThreadPool 线程池
.netFrameWork 2.0 时出现的,它是对Thread的封装。
使用方式
public static bool QueueUserWorkItem(WaitCallback callBack);
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
Task 任务
.netFrameWork 3.0 时出现的,是基于 ThreadPool的。
创建多线程的几种方式
Task.Run(() => {……});
Task.Factory.StartNew(() => {……});
new Task(() => {……}).Start();
回调
单任务回调
//执行一个任务A后回调任务B
Task.Run(() => {
//任务A
}).ContinueWith(t => {
//任务B
});
多个任务执行完回调
List
tlist.Add(Task.Run(() =>
{
Console.WriteLine("1-开始");
Thread.Sleep(1000);
Console.WriteLine("1-结束");
}));
tlist.Add(Task.Run(() =>
{
Console.WriteLine("2-开始");
Thread.Sleep(1000);
Console.WriteLine("2-结束");
}));
Task.Factory.ContinueWhenAll(tlist.ToArray(), (o) =>
{
Console.WriteLine("回调函数……");
});
主要API
对象
方法
说明
Task
WaitAll
等待全部任务完成,阻塞
Task
WaitAny
等待任意一个任务完成,阻塞
Task
WhenAll
所有任务完成返回一个Task,不阻塞
Task
WhenAny
任意一个任务完成返回一个Task,不阻塞
Task
ContinueWith
等待该任务完成,相当于回调
Task
Delay
延迟执行
TaskFactory
ContinueWhenAll
创建一个延续任务,该任务在一组指定的任务完成后开始。
TaskFactory
ContinueWhenAny
创建一个延续任务,它将在提供的组中的任何任务完成后马上开始。
Parallel 并行编程
基于 Task 实现的
卡界面
启动方式
Parallel.Invoke(() => {}, () => {});
Parallel.For(0, 5, i => {Console.WriteLine(i);});
Parallel.ForEach(new string[] { "0","1","2","3","4"}, i => {Console.WriteLine(i);});
ParallelOptions
ParallelOptions 可以控制并发数量
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 3;//最大并发3个
其它
线程异常
比如我们有下面这么一段代码,里面写了try catch的,但是没有捕捉到异常信息,这是因为线程是异步执行的,当执行到catch下面的时候,线程中还没有抛出异常,所以这个就无法正常捕获异常。
正确的写法,应该是在方法体中进行 try catch。
try
{
Task.Run(() =>
{
throw new Exception("执行失败");
})
}catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("Hi");//当执行到这儿的时候可能,线程还没有执行。
线程取消
当我们在开发过程中可能遇到这种情况,同时执行50个任务,当其中任意一个任务执行失败的时候,我们就停止其它的任务执行。
我们通过下面的例子模拟这种情况,当任务10 执行失败后,会抛出异常。已经执行但未执行完毕的任务,当检测到信号量为取消的时候,可以通过程序的判断取消执行;尚未开始执行的任务系统会自动放弃执行,并抛出异常。
try
{
List
//设置信号量
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
for (int i = 0; i < 50; i++)
{
int k = i;
taskList.Add(Task.Factory.StartNew((t) =>
{
try
{
if (t.ToString() == "10")//模拟任务10 执行失败
{
throw new Exception(string.Format("{0} 执行失败", t));
}
Thread.Sleep(200);//模拟程序操作时间
if (cancellationTokenSource.IsCancellationRequested)//判断信号量是否为取消
{
//如果信号量为取消就不执行了
Console.WriteLine(string.Format("{0} 取消执行", t));
}
else
{
Console.WriteLine(string.Format("{0} 执行成功", t));
}
}
catch (Exception ex)
{
//当执行任务异常时,将信号量设置为取消
cancellationTokenSource.Cancel();
Console.WriteLine(ex.Message);
}
}, k, cancellationTokenSource.Token));//将信号量传递给创建任务的操作。
}
Task.WaitAll(taskList.ToArray());
}
catch (AggregateException aex)//当系统检测到信号量为取消时单是队列中还有未执行的任务,会抛出这个异常
{
//因为是并行多线程的操作,所以会抛出一个异常列表。
foreach (var item in aex.InnerExceptions)
{
Console.WriteLine(item.Message);
}
}
临时变量
在使用多线程时有时会产生莫名奇妙的异常,很有可能就是因为临时变量的问题。
错误实例
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(() =>
{
Console.WriteLine(i); //输出 5 5 5 5 5。输出的信息与想象的不一样,是因为当前上下文中只有一个 i,i是不断变化的
});
}
正确实例
for (int i = 0; i < 5; i++)
{
int k = i;//每一次循环都会重新定义一个k
Task.Factory.StartNew(() =>
{
Console.WriteLine(k); //输出 0 2 1 4 3。输出的信息与想象的一致,因为当前上下文的每一个线程都有一个k,相当于每一个任务都有一个独立的k。
});
}
线程安全
当我们多个线程同时访问一个变量的时候,可能因为操作不同步造成线程间的不安全。例如以下代码:
List
int totalCount = 0;
for (int i = 0; i < 10000; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
totalCount += 1;
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(totalCount);//输出9985
Lock
优点:因为只有一个线程可以进去,解决问题
缺点:没有并发,牺牲了性能
代码
先定义一个锁对象
private static readonly object thread_Lock = new object();
然后再调用共享对象的时候使用锁包起来,就没有问题了。
taskList = new List
totalCount = 0;
for (int i = 0; i < 10000; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
lock (thread_Lock)
{
totalCount += 1;
}
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(totalCount);//输出10000
不要冲突
数据拆分,避免冲突。
如我们需要并发的时候写日志的时候,当我们将所有的日志写到一个文件的时候就会发生线程的不安全问题。但是如果使用Lock的时候又会将并发变为单线程,牺牲了性能,这种情况我们就可以将日志写到多个文件,等所有线程执行完毕之后再将多个文件合并为一个文件。
WinForm中线程问题:线程间操作无效: 从不是创建控件
可以在load时将CheckForIllegalCrossThreadCalls 属性的值设置为 false。这样进行非安全线程访问时,运行环境就不去检验它是否是线程安全的。
交给UI处理操作
this.Invoke(new Action(() =>
{
lbl.Text = text;
}));//交给UI线程去更新
应用
写线程的文章感觉也不知从何写起,里面大多数是枯燥的API,我在这里给大家举个小小的应用。
这个应用分别使用 Thread、ThreadPool 、Task、Parallel 来实现,我们或许可以从中比较出他们的差异。
需求
我之前做过一个小说网站://www.twgdh.com 这个里面的主要数据就是使用多线程抓取的。
比如我们现在要在网上抓取一本小说的内容,然后存放起来。
公共代码
HttpHelper
///
public class HttpHelper
{
public static string GetUrltoHtml(string Url, string type)
{
System.Net.WebRequest wReq = System.Net.WebRequest.Create(Url);
// Get the response instance.
System.Net.WebResponse wResp = wReq.GetResponse();
System.IO.Stream respStream = wResp.GetResponseStream();
// Dim reader As StreamReader = New StreamReader(respStream)
using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding(type)))
{
return reader.ReadToEnd();
}
}
}
FileHelper
///
public class FileHelper
{
public void CreateFile(string path, string content)
{
using (FileStream fileStream = File.Create(path))//打开文件流 (创建文件并写入)
{
StreamWriter sw = new StreamWriter(fileStream);
sw.WriteLine(content);
sw.Flush();
}
}
}
QiDianHelper
///
public class QiDianHelper
{
///
///
///
public List
(?
RegexOptions options = RegexOptions.Multiline;
foreach (Match m in Regex.Matches(html, pattern, options))
{
chapter.Content = m.Groups["content"].Value;
chapter.status = "更新成功";
return;
}
chapter.status = "更新失败";
}
}
Model
///
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C#.NET频道!
喜欢 | 0
不喜欢 | 0
您输入的评论内容中包含违禁敏感词
我知道了

请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式AI+学习就业服务平台 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号