Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for event to finish

Im trying to convert the response from the webclient to Json, but it's trying to create the JSON object before it is done downloaing it from the server. Is there a "nice" way to for me to wait for WebOpenReadCompleted to be executed?

Have to mention that this is a WP7 app, so everything is Async

public class Client
{

    public String _url;
    private String _response;
    private WebClient _web;

    private JObject jsonsobject;
    private Boolean blockingCall;


    private Client(String url)
    {
        _web = new WebClient();
        _url = url;
    }

    public JObject Login(String username, String password)
    {
        String uriUsername = HttpUtility.UrlEncode(username);
        String uriPassword = HttpUtility.UrlEncode(password);

        Connect(_url + "/data.php?req=Login&username=" + uriUsername + "&password=" + uriPassword + "");
        jsonsobject = new JObject(_response); 
        return jsonsobject;
    }

    public JObject GetUserInfo()
    {

        Connect(_url + "/data.php?req=GetUserInfo");
        jsonsobject = new JObject(_response); 
        return jsonsobject;
    }

    public JObject Logout()
    {

        Connect(_url + "/data.php?req=Logout");
        jsonsobject = new JObject(_response); 
        return jsonsobject;
    }

    private void Connect(String url)
    {

        _web.Headers["Accept"] = "application/json";
        _web.OpenReadCompleted += new OpenReadCompletedEventHandler(WebOpenReadCompleted);
        _web.OpenReadAsync(new Uri(url));
    }

    private void WebOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
    {
        if (e.Error != null || e.Cancelled)
        {
            MessageBox.Show("Error:" + e.Error.Message);
            _response = "";
        } 
        else
        {
            using (var reader = new StreamReader(e.Result))
            {
                _response = reader.ReadToEnd();
            }    
        }
    }
}
like image 224
nickknissen Avatar asked Jul 11 '12 18:07

nickknissen


1 Answers

You can use an EventWaitHandle to nicely block until the async read is complete. I had a similar requirement for downloading files with WebClient. My solution was to subclass WebClient. Full source is below. Specifically, DownloadFileWithEvents blocks nicely until the async download completes.

It should be pretty straightforward to modify the class for your purpose.

public class MyWebClient : WebClient, IDisposable
{
    public int Timeout { get; set; }
    public int TimeUntilFirstByte { get; set; }
    public int TimeBetweenProgressChanges { get; set; }

    public long PreviousBytesReceived { get; private set; }
    public long BytesNotNotified { get; private set; }

    public string Error { get; private set; }
    public bool HasError { get { return Error != null; } }

    private bool firstByteReceived = false;
    private bool success = true;
    private bool cancelDueToError = false;

    private EventWaitHandle asyncWait = new ManualResetEvent(false);
    private Timer abortTimer = null;

    const long ONE_MB = 1024 * 1024;

    public delegate void PerMbHandler(long totalMb);

    public event PerMbHandler NotifyMegabyteIncrement;

    public MyWebClient(int timeout = 60000, int timeUntilFirstByte = 30000, int timeBetweenProgressChanges = 15000)
    {
        this.Timeout = timeout;
        this.TimeUntilFirstByte = timeUntilFirstByte;
        this.TimeBetweenProgressChanges = timeBetweenProgressChanges;

        this.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(MyWebClient_DownloadFileCompleted);
        this.DownloadProgressChanged += new DownloadProgressChangedEventHandler(MyWebClient_DownloadProgressChanged);

        abortTimer = new Timer(AbortDownload, null, TimeUntilFirstByte, System.Threading.Timeout.Infinite);
    }

    protected void OnNotifyMegabyteIncrement(long totalMb)
    {
        if (NotifyMegabyteIncrement != null) NotifyMegabyteIncrement(totalMb);
    }

    void AbortDownload(object state)
    {
        cancelDueToError = true;
        this.CancelAsync();
        success = false;
        Error = firstByteReceived ? "Download aborted due to >" + TimeBetweenProgressChanges + "ms between progress change updates." : "No data was received in " + TimeUntilFirstByte + "ms";
        asyncWait.Set();
    }

    void MyWebClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        if (cancelDueToError) return;

        long additionalBytesReceived = e.BytesReceived - PreviousBytesReceived;
        PreviousBytesReceived = e.BytesReceived;
        BytesNotNotified += additionalBytesReceived;

        if (BytesNotNotified > ONE_MB)
        {
            OnNotifyMegabyteIncrement(e.BytesReceived);
            BytesNotNotified = 0;
        }
        firstByteReceived = true;
        abortTimer.Change(TimeBetweenProgressChanges, System.Threading.Timeout.Infinite);
    }

    public bool DownloadFileWithEvents(string url, string outputPath)
    {
        asyncWait.Reset();
        Uri uri = new Uri(url);
        this.DownloadFileAsync(uri, outputPath);
        asyncWait.WaitOne();

        return success;
    }

    void MyWebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        if (cancelDueToError) return;
        asyncWait.Set();
    }

    protected override WebRequest GetWebRequest(Uri address)
    {            
        var result = base.GetWebRequest(address);
        result.Timeout = this.Timeout;
        return result;
    }

    void IDisposable.Dispose()
    {
        if (asyncWait != null) asyncWait.Dispose();
        if (abortTimer != null) abortTimer.Dispose();

        base.Dispose();
    }
}
like image 195
Eric J. Avatar answered Oct 13 '22 13:10

Eric J.