C#异步协同常用例子

  • 携带私货(数据)的异步协同
    比如主线程(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不需要等待,只是发出渲染指令,能避免死锁问题。

相关推荐
范桂飓2 小时前
在 Windows GPU 机器上运行 Linux CUDA
linux·运维·windows
fengfuyao9852 小时前
C# 车牌识别系统实现
c#·车牌识别
阿登林3 小时前
C#调用钉钉API实现安全企业内部通知推送
安全·c#·钉钉
宁雨桥3 小时前
保姆级教程:windows和linux双系统的电脑如何无副作用,安全删除linux
linux·windows·电脑
咕白m6254 小时前
通过 C# 复制 Excel 工作表
c#·.net
Humbunklung5 小时前
C# 获取docx文档页数的古怪方法
开发语言·microsoft·c#
charlie1145141916 小时前
Chrome 学习小记5——demo:(动态壁纸基础)
chrome·windows·学习
北极糊的狐10 小时前
C 盘清理方法总结
windows·ios
love530love12 小时前
EPGF 架构下的 Python 环境变量设置建议——Anaconda 路径精简后暴露 python 及工具到环境变量的配置记录 [三]
开发语言·人工智能·windows·python·架构·conda·epgf 架构