我是C#异步API的新手。但是我有如何在回调函数中同步数据的问题,我在MSDN中找不到信息。
想象一下,有一个套接字包装类使用异步方式来发送和recv数据:
class MySocket {
byte[] buffer = new byte[4];
void Recv() {
_socket.BeginReceive(buffer, 0, 4, 0, new AsyncCallback(RecvCallback), null);
}
private void RecvCallback(IAsyncResult ar)
{
int n = _socket.EndReceive(ar);
if(n == 0)
// use this.buffer
}
}
RecvCallback
可能在另一个不是线程调用BeginReceive
的线程中调用,所以一般来说,在访问this
之前,它需要锁定互斥锁或其他与{{1}同步的线程。 1}}(当然它也需要锁定相同的互斥对象)。
但是在MSDN示例中,不需要锁定。我注意到,在异步启动器和回调中访问的数据都是由IAsyncResult作为回调参数传递的。
所以问题是.net lib在IAsyncResult上进行线程同步吗?我必须将以上回调更改为:
BeginReceive
更新
我发布这个问题是因为C#或(。Net)没有关于它的内存模型的文档。而Socket API doc也没有任何关于它的文字。在Java,Go,Cpp等其他语言中,我可以知道在关系之前发生了哪种情况。如果没有这种关系,用户需要自己关心线程同步。
我不知道为什么C#的规格很少。我只能根据MSDN示例猜测。在MSDN示例代码中,没有手动锁定(因此同步操作必须在某处自己完成(.NET Framework))但它是否强制用户使用AsyncState?似乎没有,但我不确定
答案 0 :(得分:0)
通常,在访问它之前,需要锁定互斥锁或其他与BeginReceive同步的东西
在多线程上下文中访问this
不会导致任何问题。通常,大多数读操作都是线程安全的。当您开始在两个线程之间共享可变状态时,您需要同步。
例如,您的代码非常好:
class MySocket {
byte[] buffer = new byte[4];
void Recv() {
_socket.BeginReceive(buffer, 0, 4, 0, new AsyncCallback(RecvCallback), null);
}
private void RecvCallback(IAsyncResult ar)
{
int n = _socket.EndReceive(ar);
if(n == 0)
// use this.buffer
}
}
另一方面,如果你要改变某种状态,你需要同步:
class MySocket {
byte[] buffer = new byte[4];
private int totalNumberOfBytes;
void Recv() {
_socket.BeginReceive(buffer, 0, 4, 0, new AsyncCallback(RecvCallback), null);
}
private void RecvCallback(IAsyncResult ar)
{
int n = _socket.EndReceive(ar);
// Needs synchronization if multiple operations happen simultaneously
totalNumberOfBytes += n;
if(n == 0)
// use this.buffer
}
}
答案 1 :(得分:0)
从我的观点来看,这个问题有两个方面:第一,this
RecvCallback
内Recv
是否与内RecvCallback
相同。其次,对AsyncCallback
的调用是否与调用线程同步。
对于第一点,必须查看RecvCallback
委托是如何从方法Target
创建的。根据{{3}},默认情况下,委托在其this
属性中封装创建它的实例,然后在调用委托时将其作为AsyncState
参数传递。因此,无需将this_is_this
提取为for
。
对于第二点:调用回调时没有自动同步,因为没有可用的同步上下文。因此,必须在回调中手动完成此同步。
答案 2 :(得分:0)
我做了一些测试以找出:
纯异步函数调用不会为用户执行线程同步。
public class main
{
struct SharedData
{
public int a;
public int b;
}
static SharedData d = new SharedData();
public void TestMethod()
{
if (d.a != d.b)
Console.WriteLine(d.a+" " +d.b);
}
public void callback(System.IAsyncResult ar)
{
d.a -= 1;
d.b -= 1;
}
static AsyncMethodCaller caller;
static void Main(string[] args)
{
d.a = 99999999;
d.b = 99999999;
main a = new main();
caller = new AsyncMethodCaller(a.TestMethod);
while (true)
caller.BeginInvoke(new System.AsyncCallback(a.callback), null);
}
public delegate void AsyncMethodCaller();
}
在此示例中,共享对象很快就会损坏。
在Socket类异步操作中不会发生上述事情。我在Socket类BeginReceive和回调函数中使用相同的测试方式,发送数据频率为1毫秒,数据有效载荷为0~10 int32。我跑了大约1个小时,没有发生腐败。这应该是MSDN doc说的Socket类是线程安全的含义。但是,通常,线程安全类意味着此类的方法可以是并发的。但重点是类的方法是否与回调函数并发(不是类的方法)?通过我的测试,答案是肯定的。
通过IAsyncResult访问共享数据可以产生线程同步
的效果public class main
{
struct SharedData
{
public int a;
public int b;
}
static SharedData d = new SharedData();
public void TestMethod()
{
if (d.a != d.b)
Console.WriteLine(d.a+" " +d.b);
}
public void callback(System.IAsyncResult ar)
{
SharedData dd = (SharedData)ar.AsyncState;
dd.a -= 1;
dd.b -= 1;
}
static AsyncMethodCaller caller;
static void Main(string[] args)
{
d.a = 99999999;
d.b = 99999999;
main a = new main();
caller = new AsyncMethodCaller(a.TestMethod);
while (true)
caller.BeginInvoke(new System.AsyncCallback(a.callback), d);
}
public delegate void AsyncMethodCaller();
}
与示例1不同,腐败不会发生。