如何使用异步回调解决这些问题?

时间:2009-12-03 10:13:17

标签: c# .net winforms multithreading asynchronous

我需要并行运行5种算法,每种算法都将图像作为输入,并将图像作为输出。完成上述每个操作后,我需要显示5个输出图像。我正在使用代理执行异步回调来完成此任务。

所以,我为这5个algos创建了5个委托,并将它们称为algo1Delegate.BeginInvoke()。

算法运行正常并且也提供输出。我在显示这些图像时面临两个问题。

为了显示图像,我创建了一个ImageViewer类(其中包含带有picturebox元素的窗体)。

//ImageViewer constructor
ImageViewer(Image img, String Title)
{
    this.pictureBox1.Image = img;
    this.Text = Title;
}

我正在显示这样的图片:

void showImage(Image image, String title)
{
    ImageViewer imageviewer = new ImageViewer(image, title);
    imageviewer.Show();
}

因为我需要在算法之后显示图像。我正在为每个BeginInvoke()传递new AsyncCallback(showImage)委托作为第三个参数


private void showImage(IAsyncResult iasycResult)
{
    MessageBox.Show("white" + Thread.CurrentThread.ManagedThreadId);


    // Retrieve the `caller` delegate.
    AsyncResult asycResult = (AsyncResult)iasycResult;
    caller = (Algo1Delegate)asycResult.AsyncDelegate;//### PROBLEM!!!

    // Retrieve the  string Title that is passed in algodelegate.BeginInvoke().
    string title = (string)iasycResult.AsyncState;
    Image outputImage = caller.EndInvoke(iasycResult);

    showImage(outputImage, title);

}
  1. 我认为您可以在上面的回调函数中看到问题。它仅适用于其他4个alog的Algo1,需要将其转换为Algo2Delegate,Algo3Delegate等。因为asycResult.AsyncDelegate的类型为object。我怎么解决这个问题?我怎样才能让它适合其他人呢?

  2. imageViewer窗口“无法响应”。我不明白为什么? ImageViewer对象被初始化并显示在每个算法的同一个线程上。为什么它没有反应。

  3. 还有其他替代解决方案吗?

  4. PS:我不能为所有算法声明一个delegateType,因为输入参数存在一些差异。

    编辑:

    嗯,我的第一和第三个问题得到了足够的投入。我为每个算法使用了单独的回调。我的第二个问题仍未解决。我更改了ImageViewer()的构造函数只是为了检查它们是否在两个不同的线程上执行:

        public ImageViewer(Image img, String title)
        {
            InitializeComponent();
            if (pictureBox1.InvokeRequired) MessageBox.Show("You must Invoke()");
            else MessageBox.Show("No need of Invoke()");
    
            this.pictureBox1.Image = img;
            this.Text = title + " : Image Viewer";
        }
    

    在每种情况下都说No need of Invoke()。我不明白是什么问题。任何人都可以解决这个问题吗?我也没有得到任何执行。只是窗户变得反应迟钝。我检查算法是否造成任何麻烦。但不,他们不是。

5 个答案:

答案 0 :(得分:1)

您应该使用您需要的常用方法替换具有一致层次结构的代理。

AsyncCallbackClass caller = (AlgoDelegate)asycResult.AsyncState;

Image img = caller.DoCallBack(iAsyncResult);

然后你有一个层次结构:

class AsyncCallback1 : AsyncCallbackClass
{
   Image DoCallBack(IAsyncResult result)
   {
      // Call specific callback with specific parameters
   }
}

class AsyncCallback2 : AsyncCallbackClass
{
   Image DoCallBack(IAsyncResult result)
   {
      // Call specific callback with specific parameters
   }
}

基本上,您将构建回调作为类的层次结构,以便main方法的“签名”相同(采用IAsyncResult的方法)并返回图像,但每个“委托”的方式(现在是一个完整的类)实现调用对于每个实现都是唯一的。

看看Replace Delegate with inheritance

修改msdn page

  

如果控件的Handle是,则为true   在与...不同的线程上创建   调用线程(表示你   必须通过调用控件   一个调用方法);否则,错误。

我假设您正在ImageViewer中创建ImageBox,而ImageViewer正在回调中创建,因此,根据定义,ImageBox已由同一个线程创建,因此无需调用。

答案 1 :(得分:1)

我想不出一个干净的解决方案来解决你的问题。你必须编写这样的代码:

  AsyncResult result = (AsyncResult)iresult;
  if (result.AsyncDelegate is AsyncDelegate1) {
    (result.AsyncDelegate as AsyncDelegate1).EndInvoke(iresult);
  }
  else if (result.AsyncDelegate is AsyncDelegate2) {
    (result.AsyncDelegate as AsyncDelegate2).EndInvoke(iresult);
  }
  //etc...
  ComputationResult answer = result.AsyncState as ComputationResult;

呸。你真的应该为每个委托类型都有一个单独的回调方法。泛型方法在这里无法帮助,约束不能是委托类型。 BeginInvoke方法调用中的lambda看起来不那么好:

  var task1 = new AsyncDelegate1(Compute1);
  var result1 = new ComputationResult("task1");
  task1.BeginInvoke(42, result1, 
    new AsyncCallback((ia) => {
      AsyncResult result = ia as AsyncResult;
      (result.AsyncDelegate as AsyncDelegate1).EndInvoke(ia);
      CommonCallback(result.AsyncState as ComputationResult);
    }), 
    result1);

罗。我只使用一种委托类型来解决这个问题。 WaitCallback类型是合适的,虽然命名错误,但您应该编写一些小助手类来存储委托目标的参数,以便您可以通过WaitCallback.state参数传递它。


您的第二个问题是由于您在回调方法中创建ImageViewer实例而引发的。回调在线程池线程上执行,而不是在UI线程上执行。 InvokeRequired返回false,因为PictureBox控件是在线程池线程上创建的。但是,此线程池线程不适合显示UI组件,它不会引发消息循环。并且有错误的公寓状态。而且它很快就会终止。

当您使用在UI线程上创建的Control时,

InvokeRequired将返回正确的值(true)。例如,您的主要启动表单。或Application.OpenForms [0]。使用InvokeRequired没什么意义,但是,您知道回调在错误的线程上执行的事实。只需直接使用BeginInvoke。调用的方法应该创建ImageViewer实例。


您正在重新发明BackgroundWorker类。它完全符合您的要求。但是要注意在正确的线程上触发RunWorkerCompleted事件的细节。你应该考虑一下。

答案 2 :(得分:0)

你可以将你的调用包装成lambda表达式,然后有一个启动委托的方法:

private void run(Action<Image,Image> delegate, Image inputImage)
{
   delegate.BeginInvoke(inputImage, // all the callback stuff here );
}

然后用lambdas调用你的run方法:

run(image => algo1(image, otherVar, otherVar2));
run(image => algo2(image, otherVar, otherVar2, otherVar3, otherVar4));

等等

答案 3 :(得分:0)

几个月前我做了类似的事情,我使用的是ThreadPool:

它正在为您管理线程,并且非常容易用于不需要复杂多线程的任务。

答案 4 :(得分:0)

@ 1。您有五个代理,但您已为每个代理定义了一个通用的回调方法。因此,您将无法找到委托实际完成的内容。一种方法是为每个代表提供不同的回调方法。

@ 2您不应该从不同的线程更新UI而不是创建它。如果是,我们使用Control.Invoke确保调用被封送到UI线程。

    MethodInvoker updateImageViewer = delegate
    {
        ImageViewer imageviewer = new ImageViewer(image, title);
        imageviewer.Show();
    };

    if (this.pictureBox1.InvokeRequired)
        this.pictureBox1.Invoke(updateImageViewer);
    else
        updateImageViewer();