C#异步协同常用例子
- 携带私货(数据)的异步协同
比如主线程(UI线程)等待Task或Task等待主线程把数据传给它,用TaskCompletionSource最适合不过,例如主线程等待Task返回数据,除了将task定义为async task 子方法,还可以这样做:
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里面等待主线程传值也是可以的:
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
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实现
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[]);
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的正确性
public class BankAccount
{private decimal balance = 0;// 创建一个专用的私有锁对象,通常命名为 lockObjectprivate 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.
适合多个任务同时更新某个变量
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里面:
if (prg.InvokeRequired)
{// 在非UI线程调用时,切换到UI线程执行prg.BeginInvoke(new Action(() => prg.je100()));
}
为什么用BeginInvoke而不是this.Invoke?? BeginInvoke不需要等待,只是发出渲染指令,能避免死锁问题。