C # Асинхронный вызов без EndInvoke? - PullRequest
9 голосов
/ 21 сентября 2011

Возьмем следующие классы в качестве примера.

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-забыть').Бар-метод должен вернуться как можно скорее.Документация вокруг делегатов, BeginInvoke, EndInvoke, ThreadPool и т. Д. Не помогает мне найти решение.

Это правильное решение?

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

Ответы [ 4 ]

12 голосов
/ 22 сентября 2011

Вам не нужно звонить 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
        }
    }
}
2 голосов
/ 21 сентября 2011

Я бы сказал, что вам лучше всего использовать 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 голос
/ 22 сентября 2011

Вы можете использовать модель обратного вызова, объясненную @ Что такое AsyncCallback?

Таким образом, ваш EndInvoke будет не в bar (), а в отдельном методе обратного вызова.

В этом примере EndRead (соответствующий EndInvoke находится в методе обратного вызова с именем CompleteRead, а не в вызывающем методе TestCallbackAPM, соответствующем bar)

0 голосов
/ 21 марта 2013

Это опция:

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