none
Running Async functions RRS feed

  • Question

  • I have a Windows form application. It is called via the task scheduler in Windows. If I pass it a command line parameter it runs without the form loading. My problem is that at the end I need to close the form. The trouble is I am calling a web service and posting data to it in a async task. During the send, the task continues and the application finishes before the async web service call responds. So I have this constructor for the form load only when a specific command line parameter is passed in.

            public frmForwarder(AppParams appParams)
            {
                InitializeComponent();
                this.FormClosing += frmForwarder_FormClosing;
                LoadSettings();
                logger.WriteLog(Resources.sLogLineBreak);
                logger.WriteLog("Headless mode");
    
                RunHeadless();
    
                logger.WriteLog("End of run");
                logger.WriteLog(Resources.sLogLineBreak);
    
                if (bHidden)
                {
                    SaveSettings();
                    //Environment.Exit(0);
                }
            }
    

    If I uncomment the Environment.Exit(0); the application quits before RunHeadless has completed. The code for RunHeadless is and the RunUpdateFunction here:

            private async void RunHeadless()
            {
                Hide();
                bHidden = true;
                iReturnCode = OpenDB();
    
                if (iReturnCode == 0)
                {
                    await RunUpdateFunction("customer");
                }
            }
    
            private async Task RunUpdateFunction(string psEndFunction)
            {
                logger.WriteLog("Running update function: " + psEndFunction);
    
                switch (psEndFunction.ToLower())
                {
                    case "customer":
                        CreatePMCustomer();
                        iReturnCode = await SendCustomer(new HttpMethod("POST"), Resources.sEndpointURL + Resources.sEndpointCustomerUpdate, pmCustomer);
                        logger.WriteLog("Return code from customer update: " + iReturnCode.ToString());
                        break;
                }
            }
    

    You can ignore CreatePMCustomer as that just creates the XML objects that I need to send. The SendCustomer function is here:

            public async Task<int> SendCustomer(HttpMethod method, string requestUri, TMCustomerXML payload = null)
            {
                HttpContent content = null;
                string sTMResponse = "";
                int statusNumber = 200;         // Assume Ok
                XmlSerializer xmlSerializer = new XmlSerializer(payload.GetType());
                importResult xmlImportResult = new importResult();
                XmlSerializer xmlResponse = new XmlSerializer(xmlImportResult.GetType());
                StringWriter textWriter = new Utf8StringWriter();
                string sResponseText = "";
    
                // Serialize the payload if one is present
                if (payload != null)
                {
                    xmlSerializer.Serialize(textWriter, payload);
                    string payloadString = textWriter.ToString();
                    content = new StringContent(payloadString, Encoding.UTF8, "application/xml");
                }
    
                using (HttpClientHandler httpClientHandler = new HttpClientHandler { Credentials = new NetworkCredential(sTMLogon, sTMPassword) })
                using (HttpClient httpClient = new HttpClient(httpClientHandler))
                {
                    HttpRequestMessage request = new HttpRequestMessage(method, requestUri)
                    {
                        Content = content
                    };
    
                    try
                    {
                        HttpResponseMessage response = await httpClient.SendAsync(request);
                        
                        sTMResponse = await response.Content.ReadAsStringAsync();
                        xmlImportResult = (importResult)xmlResponse.Deserialize(new StringReader(sTMResponse));
    
                        statusNumber = (int)response.StatusCode;
                        sResponseText = "HTTP Response: " + response.ReasonPhrase + " - Response status: " + xmlImportResult.status;
                        lblStatus.Text = sResponseText;
                        logger.WriteLog(sResponseText);
    
                        switch (statusNumber)
                        {
                            case 200:
                                break;
                            case 404:
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        lblStatus.Text = ex.Message;
                    }
                }
    
                return statusNumber;
            }
    

    If I comment out the Environment.Exit(0) the form will load and wait and things work. If I uncomment it it quits before the httpClient.SendAsync(request) has had time to get the response.

    How can I get the code to wait for the response and not do it asynchronously so the response comes back and the application can close normally after the response has come back?

    Tuesday, March 12, 2019 5:01 PM

Answers

  • "If I pass it a command line parameter it runs without the form loading. My problem is that at the end I need to close the form."

    Why would you need to close the form if the command line parameter tells it not to load to begin with? If your app needs to support running with or without a UI then you need to move the logic outside the UI. Normally we put the "work" in a separate class (e.g. Processor). Then the UI, when used, creates an instance of the class and then uses it via the UI. If the UI isn't needed then the program can just create and run the processor directly. There isn't a need for any UI interaction in this case.

    I notice in your first code block that it appears to be a constructor of a form. Firstly don't do this - ever. Constructors get called when the form is created, not when it is shown. This same constructor is also called when you load it in the designer. This is going to cause problems. Defer any work outside initializing the UI fields to the OnLoad method.

    This will also resolve the second very big thing you must never do. Calling an async method from a constructor isn't recommended. More specifically C# does not support async constructor calls and therefore your constructor shouldn't do anything that is async. In theory you could call an async method and block waiting for it to finish but this stalls that call to `new` someone made which is never a good thing. Separate the creation of the object from the running of any long work. It is never IMHO a good design to call an async method from a constructor. You're just asking for trouble. Going back to my original statement about UI/processing separation, since you shouldn't be doing the async work in a UI object anyway then separating the async "processor" from the UI resolves both of these issues.

    The third issue is your RunHeadless method. Async methods should always return Task as a result except in the boundary case where that isn't possible (e.g. event handlers). Therefore your method is wrong because it doesn't return a Task. Without the Task then callers (like your main program or the UI form) don't know when the task is complete and therefore cannot react to it. This is almost always the wrong approach.

    Summary

    1) Separate the async work out of the UI into a standalone "processor" class.

    2) Change the public async method exposed by the processor to do the real work so it returns Task.

    3) Update the form constructor to remove any logic related to doing work outside the field initialization.

    4) Add/Update the OnLoad method of the form to call the processor class to start the async work. Since the OnLoad method probably shouldn't block it should be async (but void returning) and you can put your post-async work code into the method to allow post processing.

    5) For the case where your program runs without a UI then don't create a form but just create the processor and call the async method normally.

    6) If you're using C# 7.1+ then mark the Main method as async and use the standard await/async logic to do whatever work you need after the async work completes. Otherwise have your Main method block waiting for the async work to complete using the standard Wait method. If you want to support cancellation from the Main method then that requires extra code.

    Here's some uncompiled, untested code to explain the gist of what I'm talking about.

    //Example
    class Processor
    {
       public Task DoWorkAsync () {… }
    }
    
    public class frmForwarder : Form
    {
       public frmForwarder ()   
       { 
          //Init UI
       }
    
       public async void OnLoad ( EventArgs e )
       {
          //Init processor
          ...
         
          //Wait for work to complete
          await _processor.DoWorkAsync();
    
          //Back on UI thread after work completes, do more stuff
       }
    
       private Processor _processor;
    }
    
    public class Program
    {
       public static void Main ( string[] args )
       {
          //Determine if you need to run with or without UI
          if (noUI)
          {
             //Blocking call so do any prep work before you are ready to freeze the main thread
             DoWorkAsync().Wait();
          };
       }
    
       private async Task DoWorkAsync ()
       {
          //basically the same logic as in OnLoad
          var processor = new Processor();
          …
    
          await processor.DoWorkAsync();
    
          //Do postwork stuff
       }
    }


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by MrSnert Thursday, March 14, 2019 3:25 PM
    Tuesday, March 12, 2019 6:23 PM
    Moderator

All replies

  • "If I pass it a command line parameter it runs without the form loading. My problem is that at the end I need to close the form."

    Why would you need to close the form if the command line parameter tells it not to load to begin with? If your app needs to support running with or without a UI then you need to move the logic outside the UI. Normally we put the "work" in a separate class (e.g. Processor). Then the UI, when used, creates an instance of the class and then uses it via the UI. If the UI isn't needed then the program can just create and run the processor directly. There isn't a need for any UI interaction in this case.

    I notice in your first code block that it appears to be a constructor of a form. Firstly don't do this - ever. Constructors get called when the form is created, not when it is shown. This same constructor is also called when you load it in the designer. This is going to cause problems. Defer any work outside initializing the UI fields to the OnLoad method.

    This will also resolve the second very big thing you must never do. Calling an async method from a constructor isn't recommended. More specifically C# does not support async constructor calls and therefore your constructor shouldn't do anything that is async. In theory you could call an async method and block waiting for it to finish but this stalls that call to `new` someone made which is never a good thing. Separate the creation of the object from the running of any long work. It is never IMHO a good design to call an async method from a constructor. You're just asking for trouble. Going back to my original statement about UI/processing separation, since you shouldn't be doing the async work in a UI object anyway then separating the async "processor" from the UI resolves both of these issues.

    The third issue is your RunHeadless method. Async methods should always return Task as a result except in the boundary case where that isn't possible (e.g. event handlers). Therefore your method is wrong because it doesn't return a Task. Without the Task then callers (like your main program or the UI form) don't know when the task is complete and therefore cannot react to it. This is almost always the wrong approach.

    Summary

    1) Separate the async work out of the UI into a standalone "processor" class.

    2) Change the public async method exposed by the processor to do the real work so it returns Task.

    3) Update the form constructor to remove any logic related to doing work outside the field initialization.

    4) Add/Update the OnLoad method of the form to call the processor class to start the async work. Since the OnLoad method probably shouldn't block it should be async (but void returning) and you can put your post-async work code into the method to allow post processing.

    5) For the case where your program runs without a UI then don't create a form but just create the processor and call the async method normally.

    6) If you're using C# 7.1+ then mark the Main method as async and use the standard await/async logic to do whatever work you need after the async work completes. Otherwise have your Main method block waiting for the async work to complete using the standard Wait method. If you want to support cancellation from the Main method then that requires extra code.

    Here's some uncompiled, untested code to explain the gist of what I'm talking about.

    //Example
    class Processor
    {
       public Task DoWorkAsync () {… }
    }
    
    public class frmForwarder : Form
    {
       public frmForwarder ()   
       { 
          //Init UI
       }
    
       public async void OnLoad ( EventArgs e )
       {
          //Init processor
          ...
         
          //Wait for work to complete
          await _processor.DoWorkAsync();
    
          //Back on UI thread after work completes, do more stuff
       }
    
       private Processor _processor;
    }
    
    public class Program
    {
       public static void Main ( string[] args )
       {
          //Determine if you need to run with or without UI
          if (noUI)
          {
             //Blocking call so do any prep work before you are ready to freeze the main thread
             DoWorkAsync().Wait();
          };
       }
    
       private async Task DoWorkAsync ()
       {
          //basically the same logic as in OnLoad
          var processor = new Processor();
          …
    
          await processor.DoWorkAsync();
    
          //Do postwork stuff
       }
    }


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by MrSnert Thursday, March 14, 2019 3:25 PM
    Tuesday, March 12, 2019 6:23 PM
    Moderator
  • I will look into this. Thanks. I suppose this is what comes from being an old school programmer from as far back as COBOL days and being lumped with C# and having to teach yourself and picking up bad practices without realising :)
    Wednesday, March 13, 2019 7:56 AM
  • Hi

    Do you try the method that CoolDadTx provided? If it works, please post "Mark as answer" to the appropriate answer, so that it will help other members to find the solution quickly if they face a similar issue. If not, please feel free to let us know.

    Best Regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, March 14, 2019 6:54 AM
    Moderator
  • I have to say, this works like a charm. A few quirks but it runs perfectly.
    Thursday, March 14, 2019 3:25 PM