【问题标题】:C# Asynchronous call without EndInvoke?没有 EndInvoke 的 C# 异步调用?
【发布时间】:2011-09-20 22:52:18
【问题描述】:

以以下类为例。

public class A
{
   // ...
   void Foo(S myStruct){...}
}

public class B
{
   public A test;
   // ...
   void Bar()
   {
      S myStruct = new S();
      test.Foo(myStruct);
   }
}

现在,我希望方法调用 test.Foo(myStruct) 是一个异步调用(“即发即弃”)。 bar-method 需要尽快返回。关于委托、BeginInvoke、EndInvoke、ThreadPool 等的文档并没有帮助我找到解决方案。

这是一个有效的解决方案吗?

     // Is using the `EndInvoke` method as the callback delegate valid?
     foo.BeginInvoke(myStruct, foo.EndInvoke, null);

【问题讨论】:

    标签: c# asynchronous delegates action


    【解决方案1】:

    我会说你最好的选择是使用ThreadPool:

    void bar()
    {
        ThreadPool.QueueUserWorkItem(o=>
        {
            S myStruct = new S();
            test.foo(myStruct);
        });
    }
    

    这会将 sn-p 排队以在单独的线程中执行。现在您还必须注意其他事情:如果您有多个线程访问A 的同一个实例并且该实例修改了一个变量,那么您必须确保您对变量进行了正确的同步。

    public class A
    {
        private double sum;
        private volatile bool running;
        private readonly object sync;
        public A()
        {
            sum = 0.0;
            running = true;
            sync = new object();
        }
    
        public void foo(S myStruct)
        {
            // You need to synchronize the whole block because you can get a race
            // condition (i.e. running can be set to false after you've checked
            // the flag and then you would be adding the sum when you're not 
            // supposed to be).
            lock(sync)
            {
                if(running)
                {
                    sum+=myStruct.Value;
                }
            }
        }
    
        public void stop()
        {
            // you don't need to synchronize here since the flag is volatile
            running = false;
        }
    }
    

    【讨论】:

      【解决方案2】:

      您无需致电EndInvoke;不叫它仅仅意味着:

      • 您没有从方法中获得返回值。
      • 在方法执行期间抛出的任何异常都会消失。

      听起来您想“一劳永逸”,所以最简单的方法是使用匿名委托,例如:

      var del = new Action(foo.Bar);
      del.BeginInvoke(iar =>
      {
         try
         {
            del.EndInvoke(iar);
         }
         catch (Exception ex)
         {
            // Log the message?
         }
      }, null);
      

      当你执行这段代码时会发生这种情况:

      1. 为委托分配了一个新线程(简单地说)。
      2. 线程被赋予了委托del 和匿名委托(iar => ...)。
      3. 线程执行del
      4. 当它完成执行(或发生异常)时,将存储结果或异常并执行匿名委托。
      5. 在匿名委托内部,当调用 EndInvoke 时,要么返回方法的结果,要么抛出异常(如果发生)。

      请注意,上面的示例与:

      // This is pointless and is still, essentially, synchronous.
      del.EndInvoke(del.BeginInvoke(null, null));
      

      编辑:您应该始终致电End*。我从来没有发现不调用它会出现问题的场景,但这是一个实现细节,是relying on undocumented behavior.

      最后,如果抛出异常,您的解决方案将导致进程崩溃,如果您不关心异常 (del.BeginInvoke(myStruct, null, null);),您可以简单地将 null 作为委托传递。 作为最终结果例如,您正在寻找的可能是:

      public class A
      {
          // ...
          void Foo(S myStruct){...}
          void FooAsync(S myStruct)
          {
              var del = new Action<S>(Foo);
              del.BeginInvoke(myStruct, SuppressException, del);
          }
      
          static void SuppressException(IAsyncResult ar)
          {
              try
              {
                  ((Action<S>)ar.AsyncState).EndInvoke(ar);
              }
              catch
              {
                  // TODO: Log
              }
          }
      }
      

      【讨论】:

      • 没错,你不是“必须”调用EndInvoke,但如果你不这样做,你会得到内存泄漏。 stackoverflow.com/questions/1712741/…
      • @MattKlein 不,它没有。 gist.github.com/jcdickinson/9109599 。不过,SLaks 的回答有些正确,在某些情况下,一些跟踪是使用 Begin/End-Invoke 对完成的 - 一个示例是:如果您不在 Socket 操作上调用 EndInvoke,您的套接字性能计数器将完全失控(没有内存泄漏,值将非常不正确)。
      • 也许这是一个有价值的评论,可以添加到 SLaks 的答案中。
      • 它可能会帮助其他人根据这个线程它是“必须调用 EndInvoke()”的正确方法。 stackoverflow.com/questions/4585042/…
      • 如果不需要返回值,可以传递null而不是回调方法吗?我认为是这样的
      【解决方案3】:

      你可以使用@What is AsyncCallback?解释的回调模型

      这样您的 EndInvoke 将不在 bar() 中,而是在单独的回调方法中。

      例子中是EndRead(对应EndInvoke是在回调方法中调用CompleteRead而不是调用方法TestCallbackAPM对应bar)

      【讨论】:

        【解决方案4】:

        这是一个选项:

        ThreadPool.QueueUserWorkItem(bcl =>
        {
            var bcList = (List<BarcodeColumn>)bcl;
            IAsyncResult iftAR = this.dataGridView1.BeginInvoke((MethodInvoker)delegate
            {
                int x = this.dataGridView1.Rows[0].Cells.Count - 1;
                for (int i = 0; i < this.dataGridView1.Rows.Count - 1; i++)
                {
                    try
                    {
                        string imgPath = bcList[i].GifPath;
                        Image bmpImage = Image.FromFile(imgPath);
                        this.dataGridView1.Rows[i].Cells[x].Value =bmpImage;
                    }
                    catch (Exception)
                    {
                        continue;
                    }
                }
            }); 
            while (!iftAR.IsCompleted) { /* wait this*/  }
        }, barcodeList);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多