C#多线程
目前C#多线程大部分大部分都是清一色的Task,这里就先主要讲一下Task
本文主要讲解线程的启动,延迟执行,线程等待,线程的异常捕获及线程的取消
1.线程的启动:
主要有三种方式
方式一
Task task = new Task(() =>
{
System.Diagnostics.Debug.WriteLine($"Task开启了一个线程, TheadId:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
**task.Start();**
方式二
Task.Run(()=>
{
System.Diagnostics.Debug.WriteLine($"Run开启了一个线程, TheadId:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
方式三
TaskFactory factory = new TaskFactory();
factory.StartNew(()=>
{
System.Diagnostics.Debug.WriteLine($"Factory开启了一个线程, TheadId:{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
});
2. Task延迟执行方法Delay.如下方法
private void TestTask()
{
Debug.WriteLine("主线程开始");
Console.WriteLine("Task.Delay Start");
Stopwatch timer = new();
timer.Start();
Task.Delay(100).ContinueWith((s, x) =>
{
Debug.WriteLine($"ContinueWith中启动一个线程, TheadId:{Thread.CurrentThread.ManagedThreadId:00}");
}, null);
timer.Stop();
Debug.WriteLine($"Task.Delay耗时:{timer.ElapsedMilliseconds}");
Console.WriteLine("Task.Delay End");
Debug.WriteLine("主线程结束");
}
执行后的代码
主线程开始
Task.Delay耗时:0
主线程结束
ContinueWith中启动一个线程, TheadId:41
由此可见Delay延迟不会阻塞主线程,主线程执行到这里会继续执行.在delay等待一定的时间后再执行ContinueWith中的委托
3.等待一个任务完成就执行另一个动作和等待所有任务完成执行另一个动作
private void TestTask()
{
List<Task> taskList = new List<Task>();
Task task1 = Task.Run(() => DoWork("设计", 10000));
Task task2 = Task.Run(() => DoWork("框架", 5000));
Task task3 = Task.Run(() => DoWork("分析", 3000));
Task task4 = Task.Run(() => DoWork("对接", 1000));
taskList.Add(task1);
taskList.Add(task2);
taskList.Add(task3);
taskList.Add(task4);
**int index = Task.WaitAny(taskList.ToArray());**
Debug.WriteLine($"Task {index + 1} completed first.");
//Task.WaitAll(taskList.ToArray());
//Debug.WriteLine("all Task completed first.");
}
private async Task DoWork(string taskName, int delay)
{
Debug.WriteLine($"{taskName} is Start.");
await Task.Delay(delay); // 模拟工作
Debug.WriteLine($"{taskName} is completed.");
}
4.异常的捕获
先看一下下面的代码
private void ProcessException()
{
try
{
for (int i = 0; i < 20; i++)
{
string k = $"{i}";
Task.Run(() =>
{
if (k.Equals("8"))
{
throw new Exception("因为k == 8,这里就异常了");
}
else if (k.Equals("11"))
{
throw new Exception("因为k == 11,这里就异常了");
}
else if (k.Equals("15"))
{
throw new Exception("因为k == 15,这里就异常了");
}
});
}
}
catch(Exception err)
{
Debug.WriteLine(err.Message);
}
}
上述代码异常时catch中并没有输出任何东西,这说明多线程内部发生异常时try catch是不能捕获的.
需要对代码进行调整:
1.增加对多任务的等待 wait
2.在catch中增加AggregateException的捕获
代码如下
private void ProcessException()
{
try
{
List<Task> list = new();
for (int i = 0; i < 20; i++)
{
string k = $"{i}";
Task task = Task.Run(() =>
{
if (k.Equals("8"))
{
throw new Exception("因为k == 8,这里就异常了");
}
else if (k.Equals("11"))
{
throw new Exception("因为k == 11,这里就异常了");
}
else if (k.Equals("15"))
{
throw new Exception("因为k == 15,这里就异常了");
}
});
list.Add(task);
}
Task.WaitAll(list.ToArray());
}
catch(AggregateException err)
{
foreach (var inner in err.Flatten().InnerExceptions)
{
Debug.WriteLine(inner.Message);
}
}
}
这样改动后,异常最终都会在catch中输出了
5.线程的取消 应用CancellationTokenSource
一个任务长时间运行或多个任务启动执行,如何中间取消了操作,则后续的动作或线程不再继续执行
代码如下
一个任务长时间运行代码如下
private async void CancelThread()
{
CancellationTokenSource cts = new CancellationTokenSource();
// 获取 CancellationToken
CancellationToken token = cts.Token;
// 启动一个长时间运行的任务,并传递 CancellationToken
Task longRunningTask = LongRunningOperationAsync(token);
// 模拟一些主线程上的其他操作
await Task.Delay(2000); // 等待2秒
// 请求取消操作
cts.Cancel();
try
{
// 等待长时间运行的任务完成
await longRunningTask;
}
catch (OperationCanceledException ex)
{
Debug.WriteLine($"Operation was canceled: {ex.Message}");
}
}
private async Task LongRunningOperationAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)//5s
{
token.ThrowIfCancellationRequested();
Debug.WriteLine($"Working... {i + 1}");
await Task.Delay(500, token); // 模拟异步工作,并传递 CancellationToken 以支持取消
}
Debug.WriteLine("Operation completed.");
}
多个任务中间任务取消代码如下
private async void CancelThread()
{
CancellationTokenSource cts = new CancellationTokenSource();
List<Task> tasks = [];
for (int i = 0; i < 10; i++)
{
if (cts.Token.IsCancellationRequested)// 检查是否已经请求了取消
{
Debug.WriteLine($"Cancellation requested at task {i}.");
break;
}
int taskId = i;
tasks.Add(Task.Run(async () => await ExecuteTaskAsync(taskId, cts.Token), cts.Token));
if (i == 2)
{
cts.Cancel();
Debug.WriteLine($"{i} Cancellation requested.");
}
}
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
Debug.WriteLine("One or more tasks were canceled.");
}
Debug.WriteLine("All done.");
}
private async Task ExecuteTaskAsync(int taskId, CancellationToken token)
{
for (int i = 0; i < 3; i++) // 模拟任务的工作
{
token.ThrowIfCancellationRequested();
Debug.WriteLine($"Task {taskId} is working... {i + 1}");
await Task.Delay(500, token); // 模拟异步工作
}
Debug.WriteLine($"Task {taskId} completed.");
}
6.几种现象
private void TestThread()
{
for (int i = 0; i < 20; i++)
{
Task.Run(() =>
{
Thread.Sleep(1000);
Debug.WriteLine($"{i} 开始执行了");
});
}
}
这个方法的输出一直是"20 开始执行了";
修改一下后代码如下
private void TestThread()
{
for (int i = 0; i < 20; i++)
{
int k = i;
Task.Run(() =>
{
Thread.Sleep(1000);
Debug.WriteLine($"{k} 开始执行了");
});
}
}
这时,k就是序号了.