【问题标题】:How to solve these problems with Asynchronous Callback?如何用异步回调解决这些问题?
【发布时间】:2010-12-22 18:38:29
【问题描述】:

我需要并行运行 5 个算法,每个算法都将图像作为输入并将图像作为输出。每一个都完成后,我需要显示 5 个输出图像。我正在为此任务使用委托的异步回调。

所以,我为这 5 个算法创建了 5 个委托,并像 algo1Delegate.BeginInvoke() 一样调用它们。

算法运行良好并且也给出了输出。我在显示这些图像时遇到了 2 个问题。

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

//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();
}

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


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. 还有其他替代解决方案吗?

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

编辑:

嗯,我的第 1 和第 3 问题的输入已经足够了。我对这些算法中的每一个都使用了单独的回调。我的第二个问题仍未解决。我更改了 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 种不同类型的代表?难道你不能只创建一种可以用于所有 5 个任务的类型吗?
  • 因为虽然算法对输入图像进行操作。不同的算法需要不同的编号。参数。
  • 爪子,也许您可​​以将这些参数重新组合到一个类中,这将是单个委托的参数
  • 是的,我认为但基于它们的功能,这些在逻辑上并不相同。每个算法都是图像处理和模式识别不同阶段的一个实例。

标签: c# .net winforms multithreading asynchronous


【解决方案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 线程上创建的控件时,InvokeRequired 将返回正确的值 (true)。例如,您的主要启动形式。或 Application.OpenForms[0]。但是,使用 InvokeRequired 没有什么意义,您知道回调在错误的线程上执行的事实。直接使用 BeginInvoke 即可。调用的方法应该创建 ImageViewer 实例。


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

【讨论】:

  • 这是我不知道的事情。我一直认为他们应该在同一个线程上。但我不知道它必须不是 UI 线程以外的任何线程。 #1“错误线程”=>“UI线程以外的线程”? #2 那么,在任何 GUI 中,整个 GUI 是否只有 1 个线程? #3 Just use BeginInvoke directly. The invoked method should create the ImageViewer instance. 我应该如何以及在哪里使用 BeginInvoke?如果您显示代码将有很大帮助。
  • 嗯,我知道 Backgroundworkers,但我认为它会导致 5 个对象的开销。我也想尝试“硬”的方式来学习。我使用Backgroundworkers解决了它。现在为了完整起见,您能否展示如何使用我们一直在讨论的异步回调方法来做到这一点?非常感谢帮助。我从中学到了很多。
【解决方案2】:

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

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

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

看看Replace Delegate with inheritance

编辑:来自msdn page

如果控件的句柄是 在不同的线程上创建而不是 调用线程(表明你 必须通过调用控件 调用方法);否则为假。

我假设您在 ImageViewer 中创建 ImageBox,并且在回调中创建 ImageViewer,因此根据定义,ImageBox 已由同一线程创建,因此不需要调用。

【讨论】:

  • 我已经编辑了答案。希望现在很清楚......也请参阅提供的链接。
  • 已编辑,基本上我认为您实际上是在线程中创建 ImageViewer。
  • 是的,即使它们是在同一个线程上创建的。但是为什么我仍然面临问题,我该如何解决?非常感谢您的帮助。我学到了一些解决问题的新方法。
【解决方案3】:

你能把你的调用包装成 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));

等等

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    【解决方案5】:

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

    【讨论】:

    • @2:“您不应该从与创建的线程不同的线程更新 UI”。我在哪里做的?它们是在同一个线程上创建和更新的。
    • 回调与委托在同一线程上创建。因此,当您运行此语句 [ImageViewer imageviewer = new ImageViewer(image, title);] 时,它会在同一线程(即回调线程)上更新 picturebox1。虽然 ImageViewer 是在同一个线程上创建的,但实际的控件(即 PictureBox)可能是在 UI 线程上创建的。
    • 是的,我查过了。委托(异步函数)、回调函数和 imageviewer 实例化都发生在同一个线程上。你的意思是当这个线程在 imageViewer 构造函数中遇到picturebox1 = new PictureBox() 时。实际的PictureBox() 构造函数在不同的线程上执行?有什么方法可以检查吗?
    • 实际的图片框构造函数必须在表单初始化(designer.cs)期间由框架调用。我不确定您是否真的可以找出创建图片框控件的线程 ID。这通常发生在主线程上。
    猜你喜欢
    • 1970-01-01
    • 2021-03-27
    • 1970-01-01
    • 2010-10-02
    • 1970-01-01
    • 2012-05-07
    • 1970-01-01
    • 1970-01-01
    • 2020-02-22
    相关资源
    最近更新 更多