1

I need to asynchronously get data from the same page and do not block main thread.

I tried to use DownloadDataAsync method of class WebClient but it seems that it behave not truly in async manner.

For testing this I make code

    private void button1_Click(object sender, EventArgs e)
    {
        checkLink_async();
        Thread.CurrentThread.Join(5000);

        checkLink_async();
        Thread.CurrentThread.Join(5000);

        checkLink_async();
        Thread.CurrentThread.Join(5000);
    }


    /// <summary>
    /// Check the availability of IP server by starting async read from input sensors.
    /// </summary>
    /// <returns>Nothing</returns> 
    public void checkLink_async()
    {
        string siteipURL = "http://localhost/ip9212/getio.php";
        Uri uri_siteipURL = new Uri(siteipURL);

        // Send http query
        WebClient client = new WebClient();
        try
        {
            client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(checkLink_DownloadCompleted_test);
            client.DownloadDataAsync(uri_siteipURL);

            tl.LogMessage("CheckLink_async", "http request was sent");
        }
        catch (WebException e)
        {
            tl.LogMessage("CheckLink_async", "error:" + e.Message);
        }
    }

    private void checkLink_DownloadCompleted_test(Object sender, DownloadDataCompletedEventArgs e)
    {
        tl.LogMessage("checkLink_DownloadCompleted", "http request was processed");
    }

The result from log:

01:32:12.089 CheckLink_async           http request was sent
01:32:17.087 CheckLink_async           http request was sent
01:32:22.097 CheckLink_async           http request was sent
01:32:27.102 checkLink_DownloadComplet http request was processed
01:32:27.102 checkLink_DownloadComplet http request was processed
01:32:27.102 checkLink_DownloadComplet http request was processed

I expected that each started DownloadDataAsync method will run in parallel and completes during main thread code runnig (I emulate this in a code by using Thread.CurrentThread.Join). But it seems that neither of DownloadDataAsync calls were finished before button1_Click ended (despite there was plenty of time).

Is there any way to change this behavior or I should use another approach?

BorisE
  • 21
  • 1
  • 5

2 Answers2

2

There is a simple(r) explanation for what you are observing. DownloadDataAsync uses System.ComponentModel.AsyncOperation under the covers, which keeps a reference to the SynchronizationContext current at the start of the DownloadDataAsync operation, and posts back to it at completion time. This ensures that the DownloadDataCompleted event is raised on the same thread that the download was started on in applications which have a valid SynchronizationContext (i.e. Windows Forms, which, it would appear, is what you're using).

Since we're posting back to a thread which is already blocked, completion handlers have to wait for it to become available before they can execute.

Introduce SynchronizationContext.SetSynchronizationContext(null); at the start of your button1_Click handler, and you will see that completions execute as per your original expectation, before the button handler runs to completion.

EDIT

As @PeterDuniho noted, SetSynchronizationContext(null) should not be used outside of quick-and-dirty testing. A better way to prove theories revolving around the presence, or absence of a SynchronizationContext is to use a separate test app (i.e. if already working with Windows Forms, whip up a quick Console project to see how your code behaves without a SynchronizationContext). Messing with SyncronizationContext.Current has limited uses, and can land you in a world of pain.

Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • 2
    To elaborate: note that the completions all come in at the same time. This is a clear sign that all of the operations had actually completed, and it's just that the completion event couldn't be raised until you unblocked the UI thread by returning from the `button1_Click()` method. Also, to be clear: you should set the context to `null` only as a quick test of Kirill's theory...don't leave that in production code. – Peter Duniho Nov 19 '14 at 23:40
  • @PeterDuniho, I was just about to edit my answer to emphasise the dangers of `SetSynchronizationContext(null)`, but you beat me to it. 100% agreed. – Kirill Shlenskiy Nov 19 '14 at 23:42
  • Thank you for your asnswers! The reason of such behaviour is now clear. After introducing SynchronizationContext.SetSynchronizationContext(null); the async operations works as expected: 11:37:47.897 CheckLink_async http request was sent 11:37:47.898 checkLink_DownloadComplet http request was processed 11:37:52.900 CheckLink_async http request was sent – BorisE Nov 20 '14 at 08:43
  • But as you said, if this can be used only for testing, what approach should be used for production code? I need parallel background data downloading while main thread listens for external events. The only way is to move to true multithreading (ThreadPool or similiar) with usual sync WebClient.DownloadData operations in it? – BorisE Nov 20 '14 at 08:48
1

I think you are better off using the Task API of WebClient with async-await it makes your code a lot neater:

private async void button1_Click(object sender, EventArgs e)
{
    await checkLink_async();  
    await checkLink_async();
    await checkLink_async();        
}


/// <summary>
/// Check the availability of IP server by starting async read from input sensors.
/// </summary>
/// <returns>Nothing</returns> 
public async Task checkLink_async()
{
    string siteipURL = "http://localhost/ip9212/getio.php";
    // Send http query
    var client = new System.Net.WebClient();
    try
    {            
        Task t = client.DownloadDataTaskAsync(siteipURL);

        //tl.LogMessage("CheckLink_async", "http request was sent");

        await t;

        //tl.LogMessage("checkLink_DownloadCompleted", "http request was processed");
    }
    catch (System.Net.WebException e)
    {
       // tl.LogMessage("CheckLink_async", "error:" + e.Message);
    }
}
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • my target framework is .NET 4, so I can't use new C# 5 async/await syntax – BorisE Nov 20 '14 at 11:44
  • Read about `Microsoft.Bcl.Async`. Tried it. It works! More can be read from [How can I use the async keywords in a project targeting.net 4.0](http://stackoverflow.com/questions/19421878/how-can-i-use-the-async-keywords-in-a-project-targeting-net-4-0) – BorisE Nov 20 '14 at 12:34
  • 1
    where is the downloaded Data ? – Omid Farvid Aug 25 '18 at 09:08