C#线程间通信是一个常见的问题,当我们需要在多个线程间共享数据或者进行协作时,就需要使用线程间通信机制。异步机制是其中一种常用的通信方式,其可以有效避免线程阻塞的问题,并且能够方便地实现所需的功能。
本文将为大家详细讲解C#线程间通信的异步机制,包括异步编程模型(APM)、基于事件的异步编程模型(EAP)和基于任务的异步编程模型(TAP)。并且通过两个示例来说明如何使用异步机制进行线程间通信。
异步编程模型
异步编程模型(APM)
异步编程模型(APM)是.NET Framework中最早的异步编程方式,其基于回调函数实现。在APM中,当一个异步操作请求发出后,主线程将会继续执行,而异步操作则在后台执行。当异步操作完成后,将回调一个定义好的回调函数,该回调函数将会在主线程中运行,以便得到异步操作的结果。
APM具有如下的特点:
- 异步操作是由调用线程启动的,但是执行是在异步线程中完成的。
- 异步操作完成之后,需要调用一个回调函数进行结果处理。
- 发布并消费异步操作的代码非常容易出现过多的回调嵌套。
下面是一个使用APM实现线程间通信的示例:
static void Main(string[] args)
{
//创建一个异步操作对象
IAsyncResult asyncResult = Func.BeginInvoke(1000, new AsyncCallback(Completed), null);
//主线程继续执行
Console.WriteLine("主线程继续执行,等待异步操作完成...");
//等待异步操作完成
while (!asyncResult.AsyncWaitHandle.WaitOne(100))
{
//打印等待信息
Console.Write(".");
}
//通过EndInvoke获取异步操作的执行结果
var result = Func.EndInvoke(asyncResult);
//打印结果
Console.WriteLine($"\r\n异步操作完成,结果为:{result}");
Console.ReadKey();
}
//定义一个异步方法
static Func<int, int> Func = (arg) =>
{
Console.WriteLine("异步操作开始执行...");
Thread.Sleep(arg);
Console.WriteLine("异步操作执行完成。");
return arg * 2;
};
//定义一个回调函数
static void Completed(IAsyncResult asyncResult)
{
Console.WriteLine("异步操作回调函数执行。");
}
上面的示例中,我们通过创建一个异步操作对象来启动异步操作,然后在主线程中继续执行,并且在等待异步操作完成时,使用While循环来打印等待信息。当异步操作完成后,我们通过异步操作对象上的EndInvoke方法来获取异步操作的执行结果,并在主线程中进行输出。
基于事件的异步编程模型(EAP)
基于事件的异步编程模型(EAP)是对APM的一种扩展和改进,其通过使用事件来维护异步操作的执行状态和结果,并提供了更加灵活和可读性强的异步编程方式。
EAP具有如下的特点:
- 异步操作通过一个开始方法进行启动,而不需要将异步对象和回调函数作为参数传递。
- 异步操作完成后,将会触发一个事件,事件处理函数将会在主线程中处理异步操作的结果。
- EAP对于并发限制的处理更加细致和智能化。
下面是一个使用EAP实现线程间通信的示例:
static void Main(string[] args)
{
//创建一个异步对象
var worker = new BackgroundWorker();
//声明并绑定工作者、进度和完成事件
worker.DoWork += DoWorkHandler;
worker.ProgressChanged += ProgressChangedHandler;
worker.RunWorkerCompleted += RunWorkerCompletedHandler;
//启动异步操作
worker.RunWorkerAsync(1000);
//主线程继续执行
Console.WriteLine("主线程继续执行,等待异步操作完成...");
while (!_isCompleted)
{
Console.Write(".");
Thread.Sleep(100);
}
Console.ReadKey();
}
static bool _isCompleted = false;
//定义异步操作委托
static Action<int, BackgroundWorker> DoWork = (arg, worker) =>
{
Console.WriteLine("异步操作开始执行...");
Thread.Sleep(arg);
Console.WriteLine("异步操作执行完成。");
};
//定义异步操作进度回调函数
static Action<int> ProgressChanged = (progress) =>
{
Console.WriteLine($"异步操作进度为:{progress}%");
};
//定义异步操作完成回调函数
static Action<object, RunWorkerCompletedEventArgs> RunWorkerCompleted = (sender, e) =>
{
Console.WriteLine($"异步操作回调函数执行,结果为:{e.Result}");
_isCompleted = true;
};
//定义DoWork事件处理函数
static void DoWorkHandler(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
int arg = (int)e.Argument;
//调用异步操作委托,完成异步操作
DoWork(arg, worker);
}
//定义ProgressChanged事件处理函数
static void ProgressChangedHandler(object sender, ProgressChangedEventArgs e)
{
//调用进度回调函数,处理异步执行状态
ProgressChanged(e.ProgressPercentage);
}
//定义RunWorkerCompleted事件处理函数
static void RunWorkerCompletedHandler(object sender, RunWorkerCompletedEventArgs e)
{
//调用完成回调函数,处理异步执行结果
RunWorkerCompleted(sender, e);
}
上面的示例中,我们通过创建一个BackgroundWorker对象来启动异步操作,然后声明和绑定处理异步操作的工作者,进度和完成事件,并在这些事件处理函数中完成异步执行状态和结果的处理。在主线程中,我们通过轮询_isCompleted变量的值来等待异步操作的完成。当异步操作完成后,我们在主线程中输出异步操作的结果。
基于任务的异步编程模型(TAP)
基于任务的异步编程模型(TAP)是.NET Framework中最新的异步编程方式,其建立在Task Parallel Library之上。TAP将异步操作抽象为一个Task对象,开发者不需要手动创建异步回调函数,只需要编写相应的异步方法,返回一个Task对象即可。
TAP具有如下的特点:
- 在执行异步操作时,会返回一个Task对象,该任务对象可以在需要等待异步操作完成时进行await。
- TAP提供了一组可操作的方法和属性,可以帮助开发者控制异步操作的状态和结果。
- TAP对于异步操作的返回值和状态具有更好的类型安全性和可读性。
下面是一个使用TAP实现线程间通信的示例:
static async Task Main(string[] args)
{
Console.WriteLine("启动异步操作...");
var result = await FuncAsync(1000);
Console.WriteLine($"异步操作执行完成,结果为:{result}");
Console.ReadKey();
}
//定义异步操作方法
static Task<int> FuncAsync(int arg)
{
Console.WriteLine("异步操作开始执行...");
return Task.Run(() =>
{
Thread.Sleep(arg);
Console.WriteLine("异步操作执行完成。");
return arg * 2;
});
}
上面的示例中,我们通过在Main方法标记async和await关键字来启动异步操作,并定义了一个名为FuncAsync的异步方法,该方法返回一个Task对象。在异步方法中,我们通过Task.Run方法来启动异步操作,并且将异步计算的结果返回。
TAP通过采用语义化丰富的API设计,可以简化异步代码的编写和复杂度,因此通常是最佳的异步编程方式。
示例
下面是两个使用异步机制实现线程间通信的示例:
异步委托示例
static void Main(string[] args)
{
//创建一个AsyncDelegate对象
var asyncDelegate = new AsyncDelegate();
//启动异步操作
asyncDelegate.StartAsync(1000, (result) =>
{
Console.WriteLine($"异步操作完成,结果为:{result}");
});
Console.WriteLine("主线程继续执行...");
Console.ReadKey();
}
public delegate int AsyncFunc(int arg);
//定义AsyncDelegate异步委托
public class AsyncDelegate
{
//定义异步委托
private AsyncFunc _asyncFunc;
//定义开始异步操作的方法
public void StartAsync(int arg, Action<int> callback)
{
if (_asyncFunc == null)
_asyncFunc = new AsyncFunc(this.Process);
//开始异步操作
_asyncFunc.BeginInvoke(arg, (asyncResult) =>
{
//获取结果
var result = _asyncFunc.EndInvoke(asyncResult);
//回调函数
callback(result);
}, null);
}
//定义异步操作的执行方法
private int Process(int arg)
{
Console.WriteLine("异步操作开始执行...");
Thread.Sleep(arg);
Console.WriteLine("异步操作执行完成。");
return arg * 2;
}
}
上面的示例通过使用异步委托来实现线程间通信,并且通过StartAsync方法来启动异步操作。在异步操作完成后,我们通过定义好的回调函数来处理异步操作的结果。这种方式相对于APM模式来说,能够更好的避免回调函数嵌套的问题,但是使用起来也相对略微麻烦。
TPL示例
static void Main(string[] args)
{
//启动异步操作
var task = Task.Run(() =>
{
Console.WriteLine("异步操作开始执行...");
Thread.Sleep(1000);
Console.WriteLine("异步操作执行完成。");
return 123;
});
//使用await等待异步操作的完成并获取结果
var result = task.GetAwaiter().GetResult();
Console.WriteLine($"异步操作完成,结果为:{result}");
Console.ReadKey();
}
上面的示例通过使用TPL来实现线程间通信,我们在任务计算完成后,使用await关键字等待异步操作的完成,然后获取异步操作的结果并输出。使用TPL编写异步代码简单明了,减少了回调函数的使用和嵌套,同时让异步操作的状态和处理方式更加清晰易读。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#线程间通信的异步机制 - Python技术站