C# 异步调用是否可以不使用 EndInvoke 方法?

11

以下面的课程为例。

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 方法需要尽快返回。关于委托、BeginInvoke、EndInvoke、线程池等的文档并没有帮助我找到解决方案。

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

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

13

您不需要调用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*。我从未遇到过不调用它会出现问题的情况,但这是一个实现细节,依赖于未记录的行为。

最后,如果抛出异常,你的解决方案将使进程崩溃,如果你不关心异常,可以简单地将委托传递为null (del.BeginInvoke(myStruct, 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,但如果你不这样做,就会导致内存泄漏。https://dev59.com/LHI-5IYBdhLWcg3wta7w?rq=1 - Matt Klein
@MattKlein 不,它不会。https://gist.github.com/jcdickinson/9109599。SLaks的答案在某些情况下是正确的,例如:如果您不在Socket操作上调用EndInvoke,则套接字性能计数器将完全失控(没有内存泄漏,值只是极不准确)。 - Jonathan Dickinson
也许这是一个有价值的评论,可以添加到SLaks的答案中。 - Matt Klein
根据此线程,调用“必须调用EndInvoke()”是正确的方法,这可能会帮助其他人。https://dev59.com/xW455IYBdhLWcg3wAvXQ - Sai
如果您不需要返回值,可以传递null而不是回调方法吗?我认为这应该是可以的。 - James Joshua Street

3
我认为您最好的选择是使用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;
    }
}

1

你可以使用回调模型,详见什么是AsyncCallback?

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

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


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接