Windows Phone の WebRequest のキャンセルとタイムアウト

Windows Phone の WebRequest にはタイムアウトの設定がありません。
タイムアウト機構は呼び出し側が独自に実装する必要があります。


今回の例はWebRequestの非同期リクエストとWaitHandleを組み合わせることでタイムアウトとキャンセルを提供します。

複数の条件に対する待機を WaitHandle.WaitAny で束ねることで、待機完了後の判断に曖昧さがなくなるのがポイントです。

// 上流の別スレッドから、処理のキャンセル要求があった場合にセットされる
var cancel_wait_handle = new ManualResetEvent( false );  
// 非同期リクエストが完了したらセットされる
var response_wait_handle = new ManualResetEvent( false );
var list_wait_handle = new WaitHandle[] { cancel_wait_handle,response_wait_handle };

// リクエストを送る
response_wait_handle.Reset();
var async_result = req.BeginGetResponse( (ar)=>{ response_wait_handle.Set(); },null );
switch( WaitHandle.WaitAny( list_wait_handle,timeout_ms) ){
case WaitHandle.WaitTimeout: // タイムアウトした
	req.Abort();
	result.last_status = 0;
	result.last_error = "Connect Timeout";
	log.e( "Connect Timeout. url={0}",url );
	continue;
case 0: // キャンセルされた
	req.Abort();
	log.e( "HTTP request was cancelled.");
	return null;
}

(中略)

// レスポンスのコンテンツを読む
var src = res.GetResponseStream();
if( src != null ){
	try{
		var content_length_reported = (int)res.ContentLength;
		if( content_length_reported <=4096 ) content_length_reported = 4096;
		using( var dst = new MemoryStream( content_length_reported ) ) {
			Boolean bTimeout = false;
			for( ; ; ) {
				response_wait_handle.Reset();
				async_result = src.BeginRead( tmp,0,tmp.Length,( ar ) => { response_wait_handle.Set(); },null );
				switch( WaitHandle.WaitAny( list_wait_handle,timeout_ms ) ) {
				case WaitHandle.WaitTimeout: // タイムアウトした
					result.last_status = 0;
					result.last_error = "Read Timeout";
					log.e( "Read Timeout. url={0}",url );
					bTimeout = true;
					break;
				case 0: // キャンセルされた
					log.e( "HTTP request was cancelled." );
					return null;
				}
				int delta = src.EndRead( async_result );
				if( delta <= 0 ) break;
				dst.Write( tmp,0,delta );
			}
			if( bTimeout ) continue;

			// レスポンスボディ読み取り成功
			result.content_bytes = dst.ToArray();
		}
	}catch(IOException ex){
		result.last_status = 0;
		result.last_error= TextUtil.format_ex(ex);
		log.ex_short(ex,"Fail to read response body");
		continue;
	}finally{
		src.Close();
	}
}

なお、このコードは全体としてはブロッキングするようになっているので UIスレッドから実行してはいけません。