使用AsyncCallback时线程数据同步

时间:2018-03-05 07:01:29

标签: c# multithreading

我是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?似乎没有,但我不确定

3 个答案:

答案 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 RecvCallbackRecv是否与内RecvCallback相同。其次,对AsyncCallback的调用是否与调用线程同步。

对于第一点,必须查看RecvCallback委托是如何从方法Target创建的。根据{{​​3}},默认情况下,委托在其this属性中封装创建它的实例,然后在调用委托时将其作为AsyncState参数传递。因此,无需将this_is_this提取为for

对于第二点:调用回调时没有自动同步,因为没有可用的同步上下文。因此,必须在回调中手动完成此同步。

答案 2 :(得分:0)

我做了一些测试以找出:

  1. 纯异步函数调用不会为用户执行线程同步。

    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();
    }
    
  2. 在此示例中,共享对象很快就会损坏。

    1. 在Socket类异步操作中不会发生上述事情。我在Socket类BeginReceive和回调函数中使用相同的测试方式,发送数据频率为1毫秒,数据有效载荷为0~10 int32。我跑了大约1个小时,没有发生腐败。这应该是MSDN doc说的Socket类是线程安全的含义。但是,通常,线程安全类意味着此类的方法可以是并发的。但重点是类的方法是否与回调函数并发(不是类的方法)?通过我的测试,答案是肯定的。

    2. 通过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不同,腐败不会发生。