6

Webクライアントからの応答を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();
            }    
        }
    }
}
4

2 に答える 2

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();
    }
}
于 2012-07-11T18:39:27.977 に答える
2

OpenReadAsync()を使用しているようです。これは非同期メソッドです。つまり、ハンドラーの実行中に呼び出し元のスレッドが中断されることはありません。

これは、WebOpenReadCompleted()がまだ実行されている間に、割り当て操作の設定jsonsobjectが発生することを意味します。

Connect(string url)メソッドでOpenReadAsync(new Uri(url))をOpenRead(new Uri(url))に置き換えるのが最善の策だと思います。

OpenRead()は同期操作であるため、呼び出し元のメソッドは、WebOpenReadCompleted()メソッドが完了するまで待機してから、Connect()メソッドで割り当てが発生します。

于 2012-07-11T18:41:09.913 に答える