- Task用法
- 阻塞
- 线程锁
- 线程异常
- 线程取消
- await
Task
多线程的几种用法大体上都是相似的,首先我们查看Task的定义:
如果不想一辈子做一个Coder的话,在使用新知识的时候有些东西需要去挖掘,就拿Task来说,我们可以看到一些信息:
1.Task是一个非静态的类,在使用的时候需要声明。
2.Task的构造函数是有参数的。
3.Task需要一个委托来作为参数,这个委托是没有返回值的。
4.在我们不知道重载是干什么的时候我们可以查看对应参数的定义,比如说:CancellationToken:传播有关应取消操作的通知。可能解释的不是很明确,但是通过查看CancellationToken可以看到,这个参数如果使用需要实例化,而且在使用的时候需要跟System.Threading.CancellationTokenSource相关联;TaskCreationOptions是一个枚举,每种枚举对应一种模式。
具体的用法:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
internal class ThreadClass
{
public ThreadClass()
{
for (int i = 0; i < 10; i++)
{
var name = string.Format("Param{0}", i);
Action action = () => this.DoSomething(name);
Task task = new Task(action);
task.Start();
//跟多线程对比
//ThreadStart threadStart = () => this.DoSomething(name);
//Thread thread = new Thread(threadStart);
//thread.Start();
}
}
private void DoSomething(string threadName)
{
Console.WriteLine("DoSomething start:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
Thread.Sleep(1000);
Console.WriteLine("DoSomething end:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
}
}
}
TaskFactory:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
internal class ThreadClass
{
public ThreadClass()
{
TaskFactory taskFactory = new TaskFactory();
for (int i = 0; i < 10; i++)
{
var name = string.Format("Param{0}", i);
Action action = () => this.DoSomething(name);
//这句话的效果等同于声明一个参数为action的对象;执行(Start)
Task task = taskFactory.StartNew(action);
//还可以作为有参
Action<object> act = t =>
{
this.TestThread(t.ToString());
};
Task task = taskFactory.StartNew(act, name);
}
}
private void DoSomething()
{
Console.WriteLine("DoSomething:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
}
private void DoSomething(string threadName)
{
Console.WriteLine("DoSomething start:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
Thread.Sleep(1000);
Console.WriteLine("DoSomething end:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
}
}
}
阻塞(当前线程):
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
internal class ThreadClass
{
public ThreadClass()
{
TaskFactory taskFactory = new TaskFactory();
List<Task> list = new List<Task>();
for (int i = 0; i < 10; i++)
{
var name = string.Format("Param{0}", i);
Action action = () => this.DoSomething(name);
Task task = taskFactory.StartNew(action);
list.Add(task);
}
//阻塞,直到有一个线程完成
Task.WaitAny(list.ToArray());
Console.WriteLine("WaitAny:{0}", Thread.CurrentThread.ManagedThreadId);
//阻塞,直到有所有线程完成
Task.WaitAll(list.ToArray());
Console.WriteLine("WaitAll:{0}", Thread.CurrentThread.ManagedThreadId);
}
private void DoSomething(string threadName)
{
Console.WriteLine("DoSomething start:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
Thread.Sleep(1000);
Console.WriteLine("DoSomething end:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
}
}
}
========================================================================================
输出:
DoSomething start:Param0-5
DoSomething start:Param1-3
DoSomething start:Param2-6
DoSomething start:Param3-9
DoSomething start:Param7-10
DoSomething start:Param6-8
DoSomething start:Param4-4
DoSomething start:Param5-7
DoSomething start:Param8-11
DoSomething end:Param0-5
DoSomething start:Param9-5
WaitAny:1
DoSomething end:Param1-3
DoSomething end:Param2-6
DoSomething end:Param3-9
DoSomething end:Param7-10
DoSomething end:Param6-8
DoSomething end:Param4-4
DoSomething end:Param5-7
DoSomething end:Param8-11
DoSomething end:Param9-5
WaitAll:1
通过输出我们能看出来WaitAll/WaitAny是在当前线程阻塞,如果是在UI线程上使用的话,会造成卡顿。
阻塞(新开线程):
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
internal class ThreadClass
{
public ThreadClass()
{
TaskFactory taskFactory = new TaskFactory();
List<Task> list = new List<Task>();
for (int i = 0; i < 10; i++)
{
var name = string.Format("Param{0}", i);
Action action = () => this.DoSomething(name);
Task task = taskFactory.StartNew(action);
list.Add(task);
}
taskFactory.ContinueWhenAny(list.ToArray(), t => Console.WriteLine("ContinueWhenAny"));
Console.WriteLine("Any:{0}", Thread.CurrentThread.ManagedThreadId);
taskFactory.ContinueWhenAll(list.ToArray(), t => Console.WriteLine("ContinueWhenAll"));
Console.WriteLine("All:{0}", Thread.CurrentThread.ManagedThreadId);
}
private void DoSomething(string threadName)
{
Console.WriteLine("DoSomething start:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
Thread.Sleep(1000);
Console.WriteLine("DoSomething end:{1}-{0}", System.Threading.Thread.CurrentThread.ManagedThreadId, threadName);
}
}
}
========================================================================================
Any:1
All:1
DoSomething start:Param0-5
DoSomething start:Param1-4
DoSomething start:Param3-9
DoSomething start:Param2-6
DoSomething start:Param7-8
DoSomething start:Param6-3
DoSomething start:Param5-7
DoSomething start:Param4-10
DoSomething start:Param8-11
DoSomething end:Param0-5
ContinueWhenAny
DoSomething start:Param9-5
DoSomething end:Param1-4
DoSomething end:Param3-9
DoSomething end:Param2-6
DoSomething end:Param7-8
DoSomething end:Param6-3
DoSomething end:Param5-7
DoSomething end:Param4-10
DoSomething end:Param8-11
DoSomething end:Param9-5
ContinueWhenAll
关于参数可以将ContinueWhenAll/ContinueWhenAny对比AsyncCallBack的用法。在输出中我们可以看到ContinueWhenAll/ContinueWhenAny会新开一个线程取执行。
线程锁
我们在多线程中会碰到一种情况:多个线程同时执行一步操作,就会出现1+1=1的情况。为了避免这种情况的出现,在我们使用多线程的时候,需要使用线程锁,然而线程锁不是随便在哪里使用都可以的。举个例子:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ThreadPractice
{
class ThreadClass
{
public ThreadClass()
{
TaskFactory factory = new TaskFactory();
List<Task> taskList = new List<Task>();
int count = 0;
for (int i = 0; i < 100000; i++)
{
Action act = () =>
{
count += 1;
};
var task = factory.StartNew(act);
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(count);
}
}
}
===============================================================================================
输出结果:99940
如果这是for循环中是主线程在操作的情况下,我们理想的输出结果应该是100000;输出结果为99940是因为有多个线程同时执行了count+=1的操作,就有了之前说的1+1=1的情况。线程锁就是解决这种问题的。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ThreadPractice
{
class ThreadClass
{
private static object _lock = new object();
public ThreadClass()
{
TaskFactory factory = new TaskFactory();
List<Task> taskList = new List<Task>();
int count = 0;
for (int i = 0; i < 100000; i++)
{
Action act = () =>
{
lock (_lock)
{
count += 1;
}
};
var task = factory.StartNew(act);
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(count);
}
}
}
==========================================================================================
输出结果:100000
线程异常
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
class ThreadPractice
{
public ThreadPractice()
{
TaskFactory taskFactory = new TaskFactory();
List<Task> listTask = new List<Task>();
for (int i = 0; i < 10; i++)
{
var name = string.Format("name_{0}", i);
Action<object> act = t =>
{
try
{
if (t.ToString().Equals("name_2"))
{
throw new Exception("throw a error(name_2)");
}
if (t.ToString().Equals("name_5"))
{
throw new Exception("throw a error(name_5)");
}
this.TestThread(t.ToString());
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
};
Task task = taskFactory.StartNew(act, name);
listTask.Add(task);
}
Task.WaitAll(listTask.ToArray());
}
}
}
线程取消
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
class ThreadPractice
{
public ThreadPractice()
{
TaskFactory taskFactory = new TaskFactory();
List<Task> listTask = new List<Task>();
CancellationTokenSource cts = new CancellationTokenSource();
for (int i = 0; i < 10; i++)
{
var name = string.Format("name_{0}", i);
Action<object> act = t =>
{
try
{
if (t.ToString().Equals("name_2"))
{
throw new Exception("throw a error(name_2)");
}
if (t.ToString().Equals("name_5"))
{
throw new Exception("throw a error(name_5)");
}
if (cts.IsCancellationRequested)
{
Console.WriteLine("Cancel");
}
this.TestThread(t.ToString());
}
catch (Exception e)
{
cts.Cancel();
Console.WriteLine(e.Message);
}
};
Task task = taskFactory.StartNew(act, name);
listTask.Add(task);
}
Task.WaitAll(listTask.ToArray());
}
}
}
await
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadPractice
{
internal class ThreadClass
{
public ThreadClass()
{
DoSomethingAwait();
}
//在使用的await的时候需要添加async
private async void DoSomethingAwait()
{
TaskFactory taskFactory = new TaskFactory();
Console.WriteLine("当前线程:{0}", Thread.CurrentThread.ManagedThreadId);
Task task = taskFactory.StartNew(() =>
{
Console.WriteLine("Task Start_ID:{0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
Console.WriteLine("Task End_ID:{0}", Thread.CurrentThread.ManagedThreadId);
});
await task;
Console.WriteLine("Func end_ID:{0}", Thread.CurrentThread.ManagedThreadId);
}
//但是推荐使用以下
//调用方法:
//Task task = DoSomethingTaskAwait();
//await task;
private async Task DoSomethingTaskAwait()
{
TaskFactory taskFactory = new TaskFactory();
Console.WriteLine("当前线程:{0}", Thread.CurrentThread.ManagedThreadId);
Task task = taskFactory.StartNew(() =>
{
Console.WriteLine("Task Start_ID:{0}", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
Console.WriteLine("Task End_ID:{0}", Thread.CurrentThread.ManagedThreadId);
});
await task;
Console.WriteLine("Func end_ID:{0}", Thread.CurrentThread.ManagedThreadId);
}
}
}
========================================================================================
输出结果:
当前线程:1
Task Start_ID:3
Task End_ID:3
Func end_ID:1
await在使用的时候需要给方法加async,await作用为阻塞。DoSomethingTaskAwait()和DoSomethingTask所达效果一样为什么推荐使用DoSomethingTaskAwait();因为如果使用无返回值的方法DoSomethingTask(),在调用的时候系统会提醒我们在调用方法的时候,要使用await,因为方法为void类型。但是如果我们将void用Task替换,就不会出现这种警告,而且async和Task使用是是不需要有返回值的。只是将void改为了Task。