Control.BeginInvoke is NOT fire-and-forget

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Ben Voigt [C++ MVP]

    Control.BeginInvoke is NOT fire-and-forget

    As much as the CLR team assures us that it's ok to fire-and-forget
    Control.BeginIn voke, it seems it isn't. Maybe this is a bug.

    See for example: the comments in


    I was encountering a bug that disappeared when debugging. Not when a
    debugger is attached, mind you, but when I placed a breakpoint near the
    code. Adding Trace.WriteLine statements showed that the failing code was
    not even being executed. Ok, what effects can a breakpoint have? Well,
    evaluation of watch expressions, so I cleared the watch window... check,
    same behavior. And occasionally (<10%) the code worked even with the
    breakpoint removed or disabled. Must be a race condition, hitting a
    breakpoint could definitely affect thread scheduling.

    Turns out this code (now fixed) was the culprit:

    if (postProcessing != null)
    {
    new UIPermission(UI PermissionWindo w.AllWindows).A ssert();
    Control c = new Control();
    IntPtr forceHandleCrea tion = c.Handle;
    MethodInvoker finalProcessing = postProcessing + c.Dispose;
    helper.postProc essing = delegate {
    c.Invoke(finalP rocessing); };
    }

    new System.Threadin g.Thread(helper .UIThreadProc). Start();

    The thread procedure:

    public void UIThreadProc()
    {
    new UIPermission(UI PermissionWindo w.AllWindows).A ssert();
    progressDialog = new
    ProgressTracker ((ushort)fileAr ray.Length, cumulativeSize, actionMsg);
    IntPtr forceHandleCrea tion = progressDialog. Handle;
    new System.Threadin g.Thread(WorkTh readProc).Start ();
    Application.Run (progressDialog );
    if (postProcessing != null)
    postProcessing( );
    }

    I originally had c.BeginInvoke in the asynchronous method. Seems that if
    you BeginInvoke and then the calling thread ends, the call never takes
    place. Yuck!

    Do you think this is a CLR bug or it is by design?


  • =?Utf-8?B?Q2lhcmFuIE8nJ0Rvbm5lbGw=?=

    #2
    RE: Control.BeginIn voke is NOT fire-and-forget

    I can understand why this might happen but I wouldnt think it would be by
    design.
    If it is by design then I guess they expected you to call EndInvoke after
    doing more work or something.

    --
    Ciaran O''Donnell
    try{ Life(); } catch (TooDifficultException) { throw Toys(); }



    "Ben Voigt [C++ MVP]" wrote:
    As much as the CLR team assures us that it's ok to fire-and-forget
    Control.BeginIn voke, it seems it isn't. Maybe this is a bug.
    >
    See for example: the comments in

    >
    I was encountering a bug that disappeared when debugging. Not when a
    debugger is attached, mind you, but when I placed a breakpoint near the
    code. Adding Trace.WriteLine statements showed that the failing code was
    not even being executed. Ok, what effects can a breakpoint have? Well,
    evaluation of watch expressions, so I cleared the watch window... check,
    same behavior. And occasionally (<10%) the code worked even with the
    breakpoint removed or disabled. Must be a race condition, hitting a
    breakpoint could definitely affect thread scheduling.
    >
    Turns out this code (now fixed) was the culprit:
    >
    if (postProcessing != null)
    {
    new UIPermission(UI PermissionWindo w.AllWindows).A ssert();
    Control c = new Control();
    IntPtr forceHandleCrea tion = c.Handle;
    MethodInvoker finalProcessing = postProcessing + c.Dispose;
    helper.postProc essing = delegate {
    c.Invoke(finalP rocessing); };
    }
    >
    new System.Threadin g.Thread(helper .UIThreadProc). Start();
    >
    The thread procedure:
    >
    public void UIThreadProc()
    {
    new UIPermission(UI PermissionWindo w.AllWindows).A ssert();
    progressDialog = new
    ProgressTracker ((ushort)fileAr ray.Length, cumulativeSize, actionMsg);
    IntPtr forceHandleCrea tion = progressDialog. Handle;
    new System.Threadin g.Thread(WorkTh readProc).Start ();
    Application.Run (progressDialog );
    if (postProcessing != null)
    postProcessing( );
    }
    >
    I originally had c.BeginInvoke in the asynchronous method. Seems that if
    you BeginInvoke and then the calling thread ends, the call never takes
    place. Yuck!
    >
    Do you think this is a CLR bug or it is by design?
    >
    >
    >

    Comment

    • Peter Duniho

      #3
      Re: Control.BeginIn voke is NOT fire-and-forget

      On Tue, 08 Jul 2008 08:25:37 -0700, Ben Voigt [C++ MVP]
      <rbv@nospam.nos pamwrote:
      As much as the CLR team assures us that it's ok to fire-and-forget
      Control.BeginIn voke, it seems it isn't. Maybe this is a bug.
      >
      See for example: the comments in

      >
      [...]
      Do you think this is a CLR bug or it is by design?
      First thing to keep in mind: the assertion about Control.EndInvo ke() has
      to do with resource cleanup and whether one is required to call that
      method to ensure things are cleaned up. It's not about whether
      Control.BeginIn voke() will work.

      Second, it would be helpful if you'd actually post a concise-but-complete
      code sample that reliably demonstrates the problem. Saying "I original
      had c.BeginInvoke in the asynchronous method" doesn't tell us much about
      how you actually used it or what might have been going wrong.

      I am relatively confident that if you call BeginInvoke() from a thread
      that exits before the invoked delegate gets to run, the invoked delegate
      should still run. I would be very surprised if that wasn't actually what
      happened. On the other hand, if the thread that _owns_ the control being
      used to call BeginInvoke() exits or is otherwise terminated, I would _not_
      expect the delegate being invoked to execute, since it has to execute on
      that thread.

      Again, a complete code sample would eliminate these ambiguities in your
      comment. It's impossible to tell for sure from the code you posted what
      exactly you were trying to do and what broke. It also doesn't help that
      the code you posted is clearly a corner case, whatever else might have
      been going on, and you didn't post enough to show us that you've correctly
      set the threading model for whatever threads wind up with a message pump
      (something that could also break things).

      Given the evidence so far, I cannot imaging being able to confidently say
      there's a bug, whether in the CLR or (as is probably more likely, assuming
      this is a bug at all) in the framework.

      Pete

      Comment

      • Linda Liu[MSFT]

        #4
        RE: Control.BeginIn voke is NOT fire-and-forget

        Thanks Pete and Ciaran for your help!

        Hi Ben,

        The Control.BeginIn voke method executes the specified delegate
        asynchronously on the thread that the control's underlying handle was
        created on. It means that the calling thread doen't need to wait until the
        UI thread finishes processing the request and will returns immediately.

        It's true that you should always call a delegate's EndInvoke after a call
        to a delegate's BeginInvoke. It's completely safe to call
        Control.BeginIn voke without ever calling Control.EndInvo ke, because it
        doesn't create the same resources associated with a delegate's BeginInvoke
        call.

        Even if you do want the results from a call to Control.BeginIn voke, there's
        no way to pass a callback, so you need to use the IAsyncResult
        implementation as returned from Control.BeginIn voke. You keep checking the
        IsCompleted property for true during your other worker thread processing
        before calling Control.EndInvo ke to harvest the result. This is such a pain
        that, if you want results from the call to the UI thread, I suggest that
        the worker thread use Control.Invoke instead.
        As much as the CLR team assures us that it's ok to fire-and-forget
        Control.BeginIn voke, it seems it isn't.

        Could you please show us a complete code snippet to demonstrate the problem?

        I look forward to your reply!

        Sincerely,
        Linda Liu
        Microsoft Online Community Support

        Delighting our customers is our #1 priority. We welcome your comments and
        suggestions about how we can improve the support we provide to you. Please
        feel free to let my manager know what you think of the level of service
        provided. You can send feedback directly to my manager at:
        msdnmg@microsof t.com.

        =============== =============== =============== =====
        Get notification to my posts through email? Please refer to
        Gain technical skills through documentation and training, earn certifications and connect with the community

        ications.

        Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
        where an initial response from the community or a Microsoft Support
        Engineer within 1 business day is acceptable. Please note that each follow
        up response may take approximately 2 business days as the support
        professional working with you may need further investigation to reach the
        most efficient resolution. The offering is not appropriate for situations
        that require urgent, real-time or phone-based interactions or complex
        project analysis and dump analysis issues. Issues of this nature are best
        handled working with a dedicated Microsoft Support Engineer by contacting
        Microsoft Customer Support Services (CSS) at
        http://msdn.microsoft.com/subscripti...t/default.aspx.
        =============== =============== =============== =====
        This posting is provided "AS IS" with no warranties, and confers no rights.

        Comment

        • Ben Voigt [C++ MVP]

          #5
          Re: Control.BeginIn voke is NOT fire-and-forget

          Peter Duniho wrote:
          On Tue, 08 Jul 2008 08:25:37 -0700, Ben Voigt [C++ MVP]
          <rbv@nospam.nos pamwrote:
          >
          >As much as the CLR team assures us that it's ok to fire-and-forget
          >Control.BeginI nvoke, it seems it isn't. Maybe this is a bug.
          >>
          >See for example: the comments in
          >http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx
          >>
          >[...]
          >Do you think this is a CLR bug or it is by design?
          >
          First thing to keep in mind: the assertion about Control.EndInvo ke()
          has to do with resource cleanup and whether one is required to call
          that method to ensure things are cleaned up. It's not about whether
          Control.BeginIn voke() will work.
          >
          Second, it would be helpful if you'd actually post a
          concise-but-complete code sample that reliably demonstrates the
          problem. Saying "I original had c.BeginInvoke in the asynchronous
          method" doesn't tell us much about how you actually used it or what
          might have been going wrong.
          Did I write that? Sure enough.

          I should have said "in the anonymous method".

          i.e. changing the line from
          helper.postProc essing = delegate { c.Invoke(finalP rocessing); };
          back to
          helper.postProc essing = delegate { c.BeginInvoke(f inalProcessing) ; };

          breaks things, in that the finalProcessing MulticastDelega te never runs nor
          throws an exception.
          >
          I am relatively confident that if you call BeginInvoke() from a thread
          that exits before the invoked delegate gets to run, the invoked
          delegate should still run. I would be very surprised if that wasn't
          actually what happened. On the other hand, if the thread that _owns_
          the control being used to call BeginInvoke() exits or is otherwise
          terminated, I would _not_ expect the delegate being invoked to
          execute, since it has to execute on that thread.
          The control should be owned by the original thread which does not exit, I
          read the Handle property for the explicit purpose of forcing it to be
          created on that thread, before I spawn the worker.
          >
          Again, a complete code sample would eliminate these ambiguities in
          your comment. It's impossible to tell for sure from the code you
          posted what exactly you were trying to do and what broke. It also
          doesn't help that the code you posted is clearly a corner case,
          whatever else might have been going on, and you didn't post enough to
          show us that you've correctly set the threading model for whatever
          threads wind up with a message pump (something that could also break
          things).
          Changing the call in the asynchronous method which is called at the very end
          of the worker threadproc from BeginInvoke to Invoke does cure the problem.
          I think this demonstrates that the thread on which the control is created is
          properly pumping messages.
          >
          Given the evidence so far, I cannot imaging being able to confidently
          say there's a bug, whether in the CLR or (as is probably more likely,
          assuming this is a bug at all) in the framework.
          True, it's most likely a bug in the base class libraries, not the CLR. My
          poor wording.
          >
          Pete

          Comment

          • Ben Voigt [C++ MVP]

            #6
            Re: Control.BeginIn voke is NOT fire-and-forget

            Peter Duniho wrote:
            On Tue, 08 Jul 2008 08:25:37 -0700, Ben Voigt [C++ MVP]
            <rbv@nospam.nos pamwrote:
            >
            >As much as the CLR team assures us that it's ok to fire-and-forget
            >Control.BeginI nvoke, it seems it isn't. Maybe this is a bug.
            >>
            >See for example: the comments in
            >http://blogs.msdn.com/cbrumme/archiv.../06/51385.aspx
            >>
            >[...]
            >Do you think this is a CLR bug or it is by design?
            >
            First thing to keep in mind: the assertion about Control.EndInvo ke()
            has to do with resource cleanup and whether one is required to call
            that method to ensure things are cleaned up. It's not about whether
            Control.BeginIn voke() will work.
            >
            Second, it would be helpful if you'd actually post a
            concise-but-complete code sample that reliably demonstrates the
            problem.
            I tried and it doesn't reproduce with an unloaded system. The original
            project has a few other threads of varying priorities doing unrelated tasks
            but certainly affecting thread scheduling and I can definitely understand
            why that can affect the appearance of a race condition. I'll just use
            Invoke, the thread is dying anyway, it can stay around long enough to get
            the delegate call completed message.


            Comment

            • Peter Duniho

              #7
              Re: Control.BeginIn voke is NOT fire-and-forget

              On Wed, 09 Jul 2008 06:41:10 -0700, Ben Voigt [C++ MVP]
              <rbv@nospam.nos pamwrote:
              [...]
              >Again, a complete code sample would eliminate these ambiguities in
              >your comment. It's impossible to tell for sure from the code you
              >posted what exactly you were trying to do and what broke. It also
              >doesn't help that the code you posted is clearly a corner case,
              >whatever else might have been going on, and you didn't post enough to
              >show us that you've correctly set the threading model for whatever
              >threads wind up with a message pump (something that could also break
              >things).
              >
              Changing the call in the asynchronous method which is called at the very
              end
              of the worker threadproc from BeginInvoke to Invoke does cure the
              problem.
              I think this demonstrates that the thread on which the control is
              created is
              properly pumping messages.
              Given the code that was posted, at best it demonstrates that using
              Invoke() treats the call differently than using BeginInvoke(). For
              example, it could be that there's some logic in the Invoke() method that
              winds up calling the delegate on the current thread.

              You have all the code, so you can of course make your own judgments. But
              absent a concise-but-complete code sample, I'm leery of making assumptions
              about what is specifically happening.

              The glimpse of the design that we've seen so far makes me think that
              there's probably a much better way to approach whatever you're doing
              anyway. Creating a dummy Control instance simply for the purpose of
              marshalling execution back to some thread seems very odd to me. At the
              very least, I'd think a Synchronization Context would be more appropriate,
              since it wouldn't carry all the extra unused baggage a Control has. And
              usually, when you actually need code to be marshalled back to a specific
              GUI thread, it's because you have a specific Control instance that
              requires it, and you can just use that instance to do the marshalling (in
              that case, the logic would be moved to whatever code is in your
              "postProcessing " delegate...agai n, this seems more sensible to me than
              requiring some other code to manage the marshalling arbitrarily).

              But, all that said, even if we take as granted that the design is the very
              best approach for managing this, no one here can answer the question as to
              whether there's a bug in the runtime or not. We don't have all the code,
              and so there's no way to know for sure that it's not just a bug in your
              code.

              Personally, I can count on the fingers of one hand with at least one
              finger left over the number of times that someone has said "this is a .NET
              bug" and they wound up being right. That's in spite of seeing such
              statements many dozens of times. Granted, I give you a much higher chance
              of being correct on such a statement than the average schmoe, but I'm
              still hesitant to just take the assertion at face value. I'd want to see
              the code.

              But, as long as all you want is for your code to work, I think you've got
              your solution. Based only on what you've posted, no one can say whether
              it's a .NET bug, but it doesn't sound like that's an important
              consideration for you anyway.

              Pete

              Comment

              • Ben Voigt [C++ MVP]

                #8
                Re: Control.BeginIn voke is NOT fire-and-forget

                >As far as "extra baggage" of a Control, isn't
                >cross-thread marshalling dependent on window messages anyway (hence
                >the concern for the message loop of the receiving thread), or has
                >.NET conflated
                >async calls with the UI message loop?
                >
                That I don't know. The point is that the Synchronization Context
                instance already exists, so you might as well use it rather than
                creating a whole new Control instance. :)
                Well, to answer whether window messages are used for Synchronization Context,
                I can tell you (from Reflector), that WindowsFormsSyn chronizationCon text is
                implemented on top of Control.Invoke and Control.BeginIn voke.

                Also, Control.Marshal edInvoke (the underlying implementation of Invoke and
                BeginInvoke) does use a windows message to notify the target thread. The
                delegate itself is not passed using the message data (as far as I know you
                can't safely pass addresses of garbage collectable objects asynchronously --
                ok, yes you can with GCHandle). And Invoke-ing a delegate on your own
                thread looks like it causes all invokes you've received from other threads
                to be processed immediately instead of waiting for the message loop.
                There's explicit locking everywhere and I'm glad I came up with my own
                solution for my C++/CLI IO components (where all requests are actually
                processed with APCs on a native thread running an alertable wait loop).
                Maybe I need to think about using that lockless message queue to pass
                delegates and make my own Synchronization Context implementation -- but since
                I don't control the message loop it wouldn't be nearly as elegant.


                Comment

                Working...