开发者

Trouble with editing forms in an async callback from beginInvoke of a multicast delegate

开发者 https://www.devze.com 2023-02-11 22:59 出处:网络
I have a form, shown through the ShowDialog method. This acts as a wizard and at a certain \"screen\" needs to carry out a lengthy algorithm. Executing through a multicast delegate, it produces anothe

I have a form, shown through the ShowDialog method. This acts as a wizard and at a certain "screen" needs to carry out a lengthy algorithm. Executing through a multicast delegate, it produces another thread. Here the user is allowed to use the application whilst the wizard is hidden (yeah, this is slightly strange behavious for a user interface) and the thread executes.

A callback has been passed to the thread, and this is executed when the thread finishes executing. In this procedure certain changes are made to the wizard, i.e. changing the screen displayed and state. This executes on a different thread to the one on which the wizard was created. Therefore I would expect to use MethodInvoker and then invoke another procedure to which makes the changes, however this produces the following exception:"Invoke or BeginInvoke cannot be called on a control until the window handle has been created.". Just calling the MethodInvoker does not raise an exception but causes the form to hang when displayed.

Enough rambling,code:

//code displaying the wizard
routeWizard r = new routeWizard
if (r.ShowDialog() == DialogResult.OK)
            {...}
//State change due to button clicking
 case wizardStates.finish:

    //If this has been enabled then it must be possible to be able to find a delivery list
    Cursor = Cursors.WaitCursor;
    this.Visible = false;
    findRoute();
    break;

//State change, the following procedure is called

void findRoute()
{  

    ....           
    IAsyncResult a =  ds.BeginInvoke(_customerComponent.HomeDeliveryPoint,  
               _customerComponent.HomeDeliveryPoint, nodeNumbers.ToList(),
               new AsyncCallback(EndOfSearch),null);  //Now search for a solution
     ....            

}

//and now the callback in which changes are made to the wizard
void EndOfSearch(IAsyncResult result)
{
    AsyncResult a = (AsyncResult)result;
    if (a.EndInvokeCalled == false)
    {
        MethodInvoker updateUI = new MethodInvoker(wizardEndLogic);
        /* if (this.InvokeRequired)
               Invoke(updateUI);
           else*/ //This bit in comments breaks
               updateUI();

     }
     ...
 }

 void wizardEndLogic()
 {
     this.Visible = true;
     Cursor = Cursors.Arrow;
     Next.Enabled = false;
     Cancel.Text = "Finish";
     Cancel.DialogResult = DialogRe开发者_开发技巧sult.OK;
     select.Visible = false;
     this.AcceptButton = Cancel;
     this.CancelButton = null;
     end.Visible = true;
     //end and cancel are controls on the wizard
 }

If you recall, I am fairly sure, that despite my changes, Invoke() must be used to make changes to the wizard.I've a done a bit of reserach into the exception thrown and it has been suggested that to ensure the window handle has been created the form must either be shown, which clearly it has, or by explicitly retrieving the handle from the Handle property, which I've tried. (source http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html) Neither seem to work.

Please help with this hanging form.


yeah, this is slightly strange behavious for a user interface

I would say "completely unsupported," actually. ShowDialog works by making the window modal and running a nested message loop. You're hiding the modal window, running the rest of the app from the nested message loop instead of the normal message loop, and then unhiding the window, expecting the modal window to resume. I don't believe this is supported. (I could be wrong; Raymond Chen would know).

So, here's my advice:

  1. Instead of having a "paused" modal dialog, have two. The first dialog will run up until the background operation and then exit. The second dialog will be displayed after the operation completes. These two different dialogs can be two instances of the same class.
  2. Use BackgroundWorker instead of BeginInvoke. BGW is based on SynchronizationContext, which uses its own private window handle so you don't have to worry about invalid ones.

It's not clear what ds refers to; if this is some kind of data service or domain service, and you don't control the API, then BackgroundWorker isn't an option. In this case, use Task to wrap the Begin/End API in a Task object which you can then run on a UI TaskScheduler.

If you aren't targeting .NET 4.0 yet, then there are other options (Task is not available pre-.NET 4.0). If that's the case, leave a comment and I'll go into detail.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号