- 携带私货(数据)的异步协同
比如主线程(UI线程)等待Task或Task等待主线程把数据传给它,用TaskCompletionSource最适合不过,例如主线程等待Task返回数据,除了将task定义为async task 子方法,还可以这样做:
csharp
var signal=new TaskCompletionSource<string>();
_=Task.Run(() =>
{
Console.WriteLine("task started, waiting for 5 seconds...");
Thread.Sleep(5000);
signal.SetResult("task results.");
Console.WriteLine("Task finished.");
});
Console.WriteLine("Main thread is waiting for the Task to complete...");
var result=await signal.Task;
Console.WriteLine($"task result: {result}");
当然,反过来,Task里面等待主线程传值也是可以的:
csharp
var signal = new TaskCompletionSource<string>();
_ = Task.Run(async () =>
{
Console.WriteLine("task is waiting for main thread signal...");
var result = await signal.Task;
Console.WriteLine($"result: {result}");
Console.WriteLine("Task finished.");
});
Console.WriteLine("Main thread is doing something, waiting for 5 seconds...");
Thread.Sleep(5000);
signal.SetResult("data from main thread.");
- 多次协同通知
用AutoResetEvent
csharp
var resetEvent = new AutoResetEvent(false);
var task = Task.Run(() =>
{
Console.WriteLine("task started, waiting main thread...");
resetEvent.WaitOne();
Console.WriteLine("got main thread signal,doing something..");
Console.WriteLine("wait again..");
resetEvent.WaitOne();
Console.WriteLine("got again..");
});
Console.WriteLine("Main thread is doing something, waiting for 5 seconds...");
Thread.Sleep(3000);
resetEvent.Set(); //set 1
Console.WriteLine("Main thread doing something again...");
Thread.Sleep(3000);
resetEvent.Set(); //set 2
task.Wait();
Console.WriteLine("all done.");
resetEvent.Close();
- 多个任务等待
最典型的主线程等待多任务完成 ,用CountdownEvent实现
csharp
int TaskCount = 10;
var countDown=new CountdownEvent(TaskCount);
List<Task> TaskList = new List<Task>();
for (int i = 0; i < TaskCount; i++)
{
int curI = i;
Random rnd = new Random();
Task task = Task.Run(() =>
{
Console.WriteLine($"Task {curI + 1} started");
Thread.Sleep(rnd.Next(5000));
Console.WriteLine($"Task {curI + 1} finished");
countDown.Signal();
} );
TaskList.Add(task);
}
Console.WriteLine("Main thread is waiting");
countDown.Wait();//等待所有任务结束
Console.WriteLine("All done.");
当然,如果只是等待一组任务的完成,可以用Task.WaitAll(Task[]);
csharp
int TaskCount = 10;
List<Task> TaskList = new List<Task>();
for (int i = 0; i < TaskCount; i++)
{
int curI = i;
Random rnd = new Random();
Task task = Task.Run(() =>
{
Console.WriteLine($"Task {curI + 1} started");
Thread.Sleep(rnd.Next(5000));
Console.WriteLine($"Task {curI + 1} finished");
});
TaskList.Add(task);
}
Console.WriteLine("Main thread is waiting");
Task.WaitAll(TaskList.ToArray());
Console.WriteLine("All done.");
-
多线程共享数据
volatile关键字的应用
volatile 确保被修饰的变量透明,防止因为编译器或执行程序对指令重排序导致拿不到最新的值。
可以修饰所有引用类型 (Reference types):string、object、class 等。
整数类型 (Integer types):sbyte, byte, short, ushort, int, uint。
布尔类型 (Boolean type):bool。
字符类型 (Character type):char。
浮点类型 (Floating-point types):float。
枚举类型 (Enum types):只要其基础类型是上述可修饰的类型之一。
指针类型 (Pointer types):IntPtr, UIntPtr。
唯独double,long和struct结构体不能用它 ,这些结构体应该用lock或Interlocked
lock操作
比如对银行帐户的存取操作,先定义类,类里面定义锁对象,对balance的修改都在lock里面进行以确保多线程同时操作的情况下保证balance的正确性
csharp
public class BankAccount
{
private decimal balance = 0;
// 创建一个专用的私有锁对象,通常命名为 lockObject
private readonly object lockObject = new object(); //为什么要单独定义一个lock对象??那是因为要将锁定范围最小化
public void Deposit(decimal amount)
{
// 锁定 lockObject,确保只有当前线程能进入这个代码块
lock (lockObject)
{
balance += amount;
}
}
public void Withdraw(decimal amount)
{
// 锁定 lockObject,与 Deposit 方法共享同一个锁
lock (lockObject)
{
balance -= amount;
}
}
public decimal GetBalance()
{
return balance;
}
}
Interlocked
Common Interlocked Methods
Method Description
Interlocked.Increment(ref int location) Atomically increments a 32-bit signed integer.
Interlocked.Decrement(ref int location) Atomically decrements a 32-bit signed integer.
Interlocked.Add(ref int location, int value) Atomically adds a value to a 32-bit signed integer.
Interlocked.Exchange(ref int location1, int value) Atomically sets a variable to a specified value and returns the original value.
Interlocked.CompareExchange(ref int location1, int value, int comparand) Atomically compares a variable to a comparand and, if they are equal, sets the variable to a new value. This is a common pattern for lock-free programming.
适合多个任务同时更新某个变量
csharp
public class Counter
{
private static int _count = 0;
public void DoWork()
{
// Using Interlocked ensures that the increment operation is atomic.
// It's a thread-safe way to update the shared _count variable.
Interlocked.Increment(ref _count);
// Instead of: _count++; // This is NOT thread-safe
}
public int Count => _count;
}
...
var counter = new Counter();
var tasks = new Task[100];
for (int i = 0; i < 100; i++)
{
tasks[i] = Task.Run(() => counter.DoWork());
}
Task.WaitAll(tasks);
Console.WriteLine($"Final count: {counter.Count}");
-
任务中更新UI
这个最常用,经典用法,在Task里面:
csharp
if (prg.InvokeRequired)
{
// 在非UI线程调用时,切换到UI线程执行
prg.BeginInvoke(new Action(() => prg.je100()));
}
为什么用BeginInvoke而不是this.Invoke?? BeginInvoke不需要等待,只是发出渲染指令,能避免死锁问题。