2012-07-11 148 views
6

我試圖將webclient的響應轉換爲Json,但它試圖在從服務器下載它之前創建JSON對象。 有沒有一種「很好」的方式讓我等待WebOpenReadCompleted被執行?等待事件完成

不得不提,這是一個WP7的應用程序,所以一切都是異步

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(); 
      }  
     } 
    } 
} 

回答

2

您可以使用EventWaitHandle很好地阻止,直到異步讀操作完成。我有一個與WebClient下載文件類似的要求。我的解決方案是子類WebClient。下面是完整的源代碼。具體而言,DownloadFileWithEvents很好地阻塞,直到異步下載完成。

爲了您的目的修改類應該非常簡單。

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(); 
    } 
} 
+0

EventWaitHandle在這裏使用似乎是正確的。 – nickknissen 2012-07-12 10:00:01

2

我看你使用的是OpenReadAsync()。這是一個異步方法,這意味着調用線程在處理程序執行時不會掛起。

這意味着您的賦值操作設置jsonsobject發生在WebOpenReadCompleted()仍在執行時。

我想說你最好的選擇是在Open(字符串url)方法中用OpenRead(new Uri(url))替換OpenReadAsync(new Uri(url))。

OpenRead()是一個同步操作,因此調用方法將等到WebOpenReadCompleted()方法完成後,纔會在Connect()方法中進行分配。

+0

忘了提及它是WP7應用程序,所以沒有用於webclient的同步操作,對此感到抱歉 – nickknissen 2012-07-11 18:49:34

+0

夠公平的......然後是的,顯示使用EventWaitHandle的另一個帖子是要走的路 – d3v1lman1337 2012-07-12 15:34:52