没有EndInvoke的C#异步调用?

时间:2011-09-20 22:52:19

标签: c# asynchronous delegates action

以下列课程为例。

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)成为异步调用('fire-and-forget')。条形方法需要尽快返回。代理,BeginInvoke,EndInvoke,ThreadPool等文档不能帮助我找到解决方案。

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

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

4 个答案:

答案 0 :(得分:12)

您无需致电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时,将返回该方法的结果,或者抛出异常(如果发生了异常)。
  6. 请注意,上述示例与以下内容非常不同:

    // 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
            }
        }
    }
    

答案 1 :(得分:2)

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

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

这会将片段排队以便在单独的线程中执行。现在你还需要注意其他事项:如果你有多个线程访问同一个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 :(得分:1)

您可以使用@ What is AsyncCallback?

解释的回调模型

这样你的EndInvoke不会在bar()中,而是在一个单独的回调方法中。

在示例中,EndRead(对应于EndInvoke的回调方法称为CompleteRead,而不是与bar对应的调用方法TestCallbackAPM)

答案 3 :(得分:0)

这是一个选项:

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);