6

これが基本的なセットアップです。外部 Web サービスにアクセスする必要がある Flash アプリケーションを含むページを持つ ASP.Net WebForms アプリケーションがあります。Flash の (おそらくセキュリティ上の) 制限により (質問しないでください。私は Flash の専門家ではありません)、Flash から Web サービスに直接接続することはできません。回避策は、Flash アプリケーションが呼び出すプロキシを ASP.Net で作成することです。これにより、WebService が呼び出され、結果が Flash アプリケーションに転送されます。

ただし、Web サイトのトラフィックは非常に多く、問題は、Web サービスがまったくハングした場合、ASP.Net 要求スレッドがバックアップを開始し、深刻なスレッド スタベーションにつながる可能性があることです。それを回避するために、まさにこの目的のために設計されたIHttpAsyncHandlerを使用することにしました。その中で、WebClient を使用して Web サービスを非同期的に呼び出し、応答を転送します。IHttpAsyncHandler を正しく使用する方法に関するサンプルはネット上にほとんどないので、間違っていないことを確認したいだけです。私はここに示す例に基づいて使用しています: http://msdn.microsoft.com/en-us/library/ms227433.aspx

これが私のコードです:

internal class AsynchOperation : IAsyncResult
{
    private bool _completed;
    private Object _state;
    private AsyncCallback _callback;
    private readonly HttpContext _context;

    bool IAsyncResult.IsCompleted { get { return _completed; } }
    WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }
    Object IAsyncResult.AsyncState { get { return _state; } }
    bool IAsyncResult.CompletedSynchronously { get { return false; } }

    public AsynchOperation(AsyncCallback callback, HttpContext context, Object state)
    {
        _callback = callback;
        _context = context;
        _state = state;
        _completed = false;
    }

    public void StartAsyncWork()
    {
        using (var client = new WebClient())
        {
            var url = "url_web_service_url";
            client.DownloadDataCompleted += (o, e) =>
            {
                if (!e.Cancelled && e.Error == null)
                {
                    _context.Response.ContentType = "text/xml";
                    _context.Response.OutputStream.Write(e.Result, 0, e.Result.Length);
                }
                _completed = true;
                _callback(this);
            };
            client.DownloadDataAsync(new Uri(url));
        }
    }
}

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        var asynch = new AsynchOperation(cb, context, extraData);
        asynch.StartAsyncWork();
        return asynch;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
    }

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}

これですべてうまくいきました。うまくいくはずだと思いますが、100%確実ではありません。また、独自の IAsyncResult を作成するのは少しやり過ぎのように思えます。Delegate.BeginInvoke から返された IAsyncResult を活用できる方法があるかどうか、または他の何かがあるかどうか疑問に思っています。どんなフィードバックでも大歓迎です。ありがとう!!

4

1 に答える 1

8

うわー、ええ、タスク並列ライブラリを活用することで、.NET 4.0を使用している場合、これをはるかに簡単/クリーンにすることができます。それをチェックしてください:

public class MyAsyncHandler : IHttpAsyncHandler
{
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        // NOTE: the result of this operation is void, but TCS requires some data type so we just use bool
        TaskCompletionSource<bool> webClientDownloadCompletionSource = new TaskCompletionSource<bool>();

        WebClient webClient = new WebClient())
        HttpContext currentHttpContext = HttpContext.Current;

        // Setup the download completed event handler
        client.DownloadDataCompleted += (o, e) =>
        {
            if(e.Cancelled)
            {
                // If it was canceled, signal the TCS is cacnceled
                // NOTE: probably don't need this since you have nothing canceling the operation anyway
                webClientDownloadCompletionSource.SetCanceled();
            }
            else if(e.Error != null)
            {
                // If there was an exception, signal the TCS with the exception
                webClientDownloadCompletionSource.SetException(e.Error);
            }
            else
            {
                // Success, write the response
                currentHttpContext.Response.ContentType = "text/xml";
                currentHttpContext.Response.OutputStream.Write(e.Result, 0, e.Result.Length);

                // Signal the TCS that were done (we don't actually look at the bool result, but it's needed)
                taskCompletionSource.SetResult(true);
            }
        };

        string url = "url_web_service_url";

        // Kick off the download immediately
        client.DownloadDataAsync(new Uri(url));

        // Get the TCS's task so that we can append some continuations
        Task webClientDownloadTask = webClientDownloadCompletionSource.Task;

        // Always dispose of the client once the work is completed
        webClientDownloadTask.ContinueWith(
            _ =>
            {
                client.Dispose();
            },
            TaskContinuationOptions.ExecuteSynchronously);

        // If there was a callback passed in, we need to invoke it after the download work has completed
        if(cb != null)
        {
            webClientDownloadTask.ContinueWith(
               webClientDownloadAntecedent =>
               {
                   cb(webClientDownloadAntecedent);
               },
               TaskContinuationOptions.ExecuteSynchronously);
         }

        // Return the TCS's Task as the IAsyncResult
        return webClientDownloadTask;
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        // Unwrap the task and wait on it which will propagate any exceptions that might have occurred
        ((Task)result).Wait();
    }

    public bool IsReusable
    {
        get 
        { 
            return true; // why not return true here? you have no state, it's easily reusable!
        }
    }

    public void ProcessRequest(HttpContext context)
    {
    }
}
于 2011-06-17T17:15:47.730 に答える