I still don't get Delegates

2019-03-22 03:29发布

问题:

Isn't the use of delegates to help with some asynchronous cases? I tried the following but my UI still hangs. When on earth do you use delegates?

       Public Class Form1
    Private Delegate Sub testDelegate()

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As _
 System.EventArgs) Handles Button1.Click

        Dim d As testDelegate = New testDelegate(AddressOf Add)
        d.Invoke()

    End Sub

    Private Sub Add()
        For i As Integer = 0 To 10000
            TextBox1.Text = i + 1
        Next
    End Sub
End Class

回答1:

It's kind of ironic, but everybody's favorite answer (use BeginInvoke) is not in fact correct. The delegate target method will execute on a threadpool thread. You are not allowed to touch controls on a thread other than the thread that created them, almost always the program's main thread.

If you try it using the .NET framework version 2.0 and up in the debugger, the loop will immediately terminate with an IllegalOperationException. To correct that, you'll have to use Control.BeginInvoke(). A completely different animal from the delegate's BeginInvoke() method btw.

Now here's the irony, your loop will now dispatch 10,000 delegate invocation requests to the UI thread. It will spend several seconds executing them, not getting around to doing any real work. Like dispatch the TextBox's Paint event. Or respond to any user input. You are in fact much worse off than before.

I doubt this helps much explaining delegates. Maybe you can pick a better example, something that doesn't try to update controls.



回答2:

As mentioned by Joel - BeginInvoke() will execute the delegate Asynchronously - and you will need to setup an Async callback to retrieve the return value if the target returns data (using EndInvoke).

The following link is a good article on using Delegates for Async Programming: http://msdn.microsoft.com/en-us/library/2e08f6yc.aspx

Also (this is in C# - sorry) you can handle the callback using lambda expressions:

Action myAction = () => Console.WriteLine("This is an async call!");
myAction.BeginInvoke(asyncResult => Console.WriteLine("Async Done!"), null);


回答3:

Call .BeginInvoke() rather than .Invoke().



回答4:

Here's what I sent to a coworker:

Basically delegates can be used for Callbacks, Events and Asynchronous processing.

Here’s some sample code, copy and paste each example to a new command line project.

//Callbacks    
//-------------------------------------------------------------------------------------------------------     
delegate double DiscountRule(); // Declaration
delegate string MessageFormat(); // Declaration

class Message    
{
    public string Instance() { return "You save {0:C}"; }
    public static string Class() { return "You are buying for {0:C}"; }

    public void Out(MessageFormat format, double d)
    {
        System.Console.WriteLine(format(), d);

    }
}

class Discount
{
    public static double Apply(DiscountRule rule, double amount)
    {
        return rule() * amount; // Callback
    }

    public static double Maximum() { return 0.50; }
    public static double Average() { return 0.20; }
    public static double Minimum() { return 0.10; }
    public static double None() { return 0.00; }
}

class TestDelegate1
{
    public static void Main()    
    {   
        DiscountRule[] rules = { // Instantiations  
             Discount.None,
             Discount.Minimum,    
             Discount.Average,    
             Discount.Maximum,    
             };

        // Instantiation with a static method    
        MessageFormat format = Message.Class;

        double buy = 100.00;    
        Message msg = new Message();             
        msg.Out(format, buy); // Invocation     

        // Instantiation with an instance method    
        format = msg.Instance;

        foreach (DiscountRule r in rules)    
        {    
            double saving = Discount.Apply(r, buy); // Invocation    
            msg.Out(format, saving); // Invocation    
        }    
    }    
}

//--------------------------------------------------------------------------------------------------------    
//Events:     

//the delegate creates a new type    
delegate void UpdateEventHandler();

class Subject    
{    
    private int data;

    public int GetData() { return data; }   
    public void SetData(int value) { data = value; Changed(); }    
    public event UpdateEventHandler Changed;    
}

class Observer    
{    
    public Observer(Subject s) { subject = s; }    
    public Subject GetSubject() { return subject; }   
    private Subject subject;   
}

class HexObserver : Observer
{    
    public HexObserver(Subject s)    
        : base(s)   
    {    
        s.Changed += Update;   
    }

    public void Update()    
    {   
        System.Console.Write("0x{0:X} ", GetSubject().GetData());    
    }    
}

class DecObserver : Observer    
{
     public DecObserver(Subject s)    
        : base(s)    
    {    
        s.Changed += Update;   
    }

    public void Update()    
    {    
        System.Console.Write("{0} ", GetSubject().GetData());    
    }    
}

class TestEvent    
{    
    public static void Main()    
    {   
        Subject s = new Subject();

        //assigns a Hex observer to object s (the constructor does the += assignment)    
        HexObserver ho = new HexObserver(s);

        //assigns a Decimal observer to object s    
        DecObserver co = new DecObserver(s);     

        for (int c; ; )    
        {    
            System.Console.Write("\nEnter a character" +    
            "(followed by a return, ctrl-C to exit): ");

            c = System.Console.Read();    
            s.SetData(c);    
            System.Console.Read(); // Two reads to get rid of the \r\n on PC.    
            System.Console.Read();   
        }    
    }    
}

.

//--------------------------------------------------------------------------------------------------------  
//Asynchronous processing (from http://msdn.microsoft.com/en-us/library/h80ttd5f(VS.71).aspx)         
using System;    
using System.Runtime.Remoting.Messaging;

// Create an asynchronous delegate.    
public delegate bool FactorizingAsyncDelegate (    
         int factorizableNum,     
         ref int primefactor1,    
         ref int primefactor2);

// Create a class that factorizers the number.    
public class PrimeFactorizer    
{    
   public bool Factorize(
                int factorizableNum,      
                ref int primefactor1,    
                ref int primefactor2)   
   {    
      primefactor1 = 1;    
      primefactor2 = factorizableNum;

      // Factorize using a low-tech approach.    
      for (int i=2;i<factorizableNum;i++)   
      {
         if (0 == (factorizableNum % i))    
         {
            primefactor1 = i;    
            primefactor2 = factorizableNum / i;    
            break;    
         }    
      }

      if (1 == primefactor1 )    
         return false;    
      else    
         return true   ;    
   }    
}

// Class that receives a callback when the results are available.    
public class ProcessFactorizedNumber    
{    
   private int _ulNumber;

   public ProcessFactorizedNumber(int number)   
   {    
      _ulNumber = number;   
   }

   // Note that the qualifier is one-way.    
   [OneWayAttribute()]    
   public void FactorizedResults(IAsyncResult ar)    
   {
      int factor1=0, factor2=0; 

      // Extract the delegate from the AsyncResult.      
      FactorizingAsyncDelegate fd = (FactorizingAsyncDelegate)((AsyncResult)ar).AsyncDelegate;

      // Obtain the result.    
      fd.EndInvoke(ref factor1, ref factor2, ar);

      // Output the results.    
      Console.WriteLine("On CallBack: Factors of {0} : {1} {2}",     
                   _ulNumber, factor1, factor2);    
   }    
}

// Class that shows variations of using Asynchronous    
public class Simple    
{    
   // The following demonstrates the Asynchronous Pattern using a callback.    
   public void FactorizeNumber1()    
   {
      // The following is the client code.    
      PrimeFactorizer pf = new PrimeFactorizer();    
      FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

      int factorizableNum = 1000589023, temp=0; 

      // Create an instance of the class that is going   
      // to be called when the call completes.   
      ProcessFactorizedNumber fc = new ProcessFactorizedNumber(factorizableNum);

      // Define the AsyncCallback delegate.    
      AsyncCallback cb = new AsyncCallback(fc.FactorizedResults);

      // You can use any object as the state object.    
      Object state = new Object();

      // Asynchronously invoke the Factorize method on pf.   
      IAsyncResult ar = fd.BeginInvoke(
                           factorizableNum,     
                           ref temp, 
                           ref temp,     
                           cb,     
                           state);          
      //    
      // Do some other useful work.    
      //. . .    
   }    

   // The following demonstrates the Asynchronous Pattern using a BeginInvoke, followed by waiting with a time-out.    
   public void FactorizeNumber2()    
   {    
      // The following is the client code.    
      PrimeFactorizer pf = new PrimeFactorizer();    
      FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

      int factorizableNum = 1000589023, temp=0; 

      // Create an instance of the class that is going     
      // to be called when the call completes.
      ProcessFactorizedNumber fc = new ProcessFactorizedNumber(factorizableNum);

      // Define the AsyncCallback delegate.    
      AsyncCallback cb =     
           new AsyncCallback(fc.FactorizedResults);

      // You can use any object as the state object.    
      Object state = new Object();

      // Asynchronously invoke the Factorize method on pf.   
      IAsyncResult ar = fd.BeginInvoke(   
                        factorizableNum,     
                        ref temp,     
                        ref temp,     
                        null,    
                        null); 

      ar.AsyncWaitHandle.WaitOne(10000, false);

      if (ar.IsCompleted)  
      {   
         int factor1=0, factor2=0;          

         // Obtain the result.    
         fd.EndInvoke(ref factor1, ref factor2, ar);    

         // Output the results.    
         Console.WriteLine("Sequential : Factors of {0} : {1} {2}", 
                       factorizableNum, factor1, factor2);   
      }    
   }

   public static void Main(String[] args)    
   {    
      Simple simple = new Simple();    
      simple.FactorizeNumber1();    
      simple.FactorizeNumber2();   
   }


回答5:

The best analogy that I've found to explain delegates it's a testament or your last will.

It's a set of instructions that of course you write before you die, and you leave it in a safe place. Then after your death, your attorney will execute those instructions...

Delegates are used mostly when the code that wants to execute the actions doesn't knows the enough details of what that action should be, you can view it as a sequence of actions to be executed at the appropriate time.



回答6:

In this case, I might not use a delegate, since you need to re-delegate back to the UI thread to update the buttons. You can use Application.DoEvents inside the loop to make your UI responsive while updating it.



回答7:

As Joel and Matthew mention, BeginInvoke will execute a delegate asynchronously. As Matthew says, this MSDN article covers that well.

However, if you're trying to do some background work in a WinForms application, I recommend you check out the background worker control. It will be more familiar to developers used to the WinForms event-driven model.