如何编写异步方法的包装器?

发布于 2024-12-05 18:26:54 字数 605 浏览 0 评论 0原文

我有一个 JSON API,我希望我的应用程序能够访问它。所以我写了一个方法。

public List<Books> GetBooks()
{
  var webclient = new WebClient();
  var jsonOutput = webclient.DownloadString(
                         new Uri("http://someplace.com/books.json")
                             );

  return ParseJSON(jsonOutput);//Some synchronous parsing method 
}

现在我需要将 DonwloadString 更改为 DownloadStringAsync。 我找到了这个教程

但这似乎太复杂了。我正在努力让它发挥作用,但不确定这是否是正确的方法。也许有更简单更好的方法?

I have a JSON API which I want my application to access. So I wrote a method.

public List<Books> GetBooks()
{
  var webclient = new WebClient();
  var jsonOutput = webclient.DownloadString(
                         new Uri("http://someplace.com/books.json")
                             );

  return ParseJSON(jsonOutput);//Some synchronous parsing method 
}

Now I need to change DonwloadString to DownloadStringAsync.
I found this tutorial.

But this just seems too complicated. I'm trying to get this working, but am not sure if this is the right way to go. Perhaps there is a simpler and better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

坚持沉默 2024-12-12 18:26:54

所有需要您订阅事件才能获取结果的异步操作都非常痛苦。我认为最简单的方法是将事件处理抽象为一些很好的扩展方法,并使用连续传递样式(CPS)来处理结果。

因此,第一件事是创建一个用于下载字符串的扩展方法:

public static void DownloadString(this Uri uri, Action<string> action)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = new WebClient();

    DownloadStringCompletedEventHandler handler = null;
    handler = (s, e) =>
    {
        var result = e.Result;
        webclient.DownloadStringCompleted -= handler;
        webclient.Dispose();
        action(result);
    };

    webclient.DownloadStringCompleted += handler;
    webclient.DownloadStringAsync(uri);
}

此方法隐藏了 WebClient 的创建、所有事件处理以及随后清理内容的处置和取消订阅。

它的使用方式如下:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t =>
{
    // Do something with the string
});

现在可以使用它来创建 GetBooks 方法。它是这样的:

public void GetBooks(Uri uri, Action<List<Books>> action)
{
    if (action == null) throw new ArgumentNullException("action");
    uri.DownloadString(t =>
    {
        var books = ParseJSON(t);
        action(books);
    });
}

它的用法是这样的:

this.GetBooks(new Uri("http://someplace.com/books.json"), books =>
{
    // Do something with `List<Books> books`
});

这应该是整洁和简单的。

现在,您可能希望通过几种方式扩展它。

您可以创建具有以下签名的 ParseJSON 重载:

void ParseJSON(string text, Action<List<Books>> action)

然后您可以完全取消 GetBooks 方法,只需编写以下内容:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
    // `string t` is also in scope here
}));

现在您有了一个漂亮整洁的流式风格,可组合的操作集。作为奖励,下载的字符串 t 也在范围内,因此您可以轻松记录它或在需要时进行其他处理。

您可能还需要处理异常,可以像这样添加这些异常:

public static void DownloadString(
    this Uri uri,
    Action<string> action,
    Action<Exception> exception)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = (WebClient)null;

    Action<Action> catcher = body =>
    {
        try
        {   
            body();
        }
        catch (Exception ex)
        {
            ex.Data["uri"] = uri;
            if (exception != null)
            {
                exception(ex);
            }
        }
        finally
        {
            if (webclient != null)
            {
                webclient.Dispose();
            }
        }
    };

    var handler = (DownloadStringCompletedEventHandler)null;        
    handler = (s, e) =>
    {
        var result = (string)null;
        catcher(() =>
        {   
            result = e.Result;
            webclient.DownloadStringCompleted -= handler;
        });
        action(result);
    };

    catcher(() =>
    {   
        webclient = new WebClient();
        webclient.DownloadStringCompleted += handler;
        webclient.DownloadStringAsync(uri);
    });
}

然后您可以将非错误处理 DownloadString 扩展方法替换为:

public static void DownloadString(this Uri uri, Action<string> action)
{
    uri.DownloadString(action, null);
}

然后要使用错误处理方法,您可以这样做

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
}), ex =>
{
    // Do something with `Exception ex`
});

:最终结果应该相当易于使用和阅读。我希望这有帮助。

All of the async operations that require you to subscribe to events to get the results are just painful. I think that the simplest way to go is to abstract away the event handling into some nice extension methods and use continuation passing style (CPS) to process the results.

So, the first thing is to create an extension method for downloading strings:

public static void DownloadString(this Uri uri, Action<string> action)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = new WebClient();

    DownloadStringCompletedEventHandler handler = null;
    handler = (s, e) =>
    {
        var result = e.Result;
        webclient.DownloadStringCompleted -= handler;
        webclient.Dispose();
        action(result);
    };

    webclient.DownloadStringCompleted += handler;
    webclient.DownloadStringAsync(uri);
}

This method hides away the creation of the WebClient, all of the event handling, and the disposing and unsubscribing to clean things up afterwards.

It's used like this:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t =>
{
    // Do something with the string
});

Now this can be used to create a GetBooks method. Here it is:

public void GetBooks(Uri uri, Action<List<Books>> action)
{
    if (action == null) throw new ArgumentNullException("action");
    uri.DownloadString(t =>
    {
        var books = ParseJSON(t);
        action(books);
    });
}

It's used like this:

this.GetBooks(new Uri("http://someplace.com/books.json"), books =>
{
    // Do something with `List<Books> books`
});

That should be neat and simple.

Now, you may wish to extend this a couple of ways.

You could create an overload of ParseJSON that has this signature:

void ParseJSON(string text, Action<List<Books>> action)

Then you could do away with the GetBooks method altogether and just write this:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
    // `string t` is also in scope here
}));

Now you have a nice neat fluent-style, composable set of operations. As a bonus the downloaded string, t, is also in scope so you can easily log it or do some other processing if need be.

You may also need to handle exceptions and these can be added like so:

public static void DownloadString(
    this Uri uri,
    Action<string> action,
    Action<Exception> exception)
{
    if (uri == null) throw new ArgumentNullException("uri");
    if (action == null) throw new ArgumentNullException("action");

    var webclient = (WebClient)null;

    Action<Action> catcher = body =>
    {
        try
        {   
            body();
        }
        catch (Exception ex)
        {
            ex.Data["uri"] = uri;
            if (exception != null)
            {
                exception(ex);
            }
        }
        finally
        {
            if (webclient != null)
            {
                webclient.Dispose();
            }
        }
    };

    var handler = (DownloadStringCompletedEventHandler)null;        
    handler = (s, e) =>
    {
        var result = (string)null;
        catcher(() =>
        {   
            result = e.Result;
            webclient.DownloadStringCompleted -= handler;
        });
        action(result);
    };

    catcher(() =>
    {   
        webclient = new WebClient();
        webclient.DownloadStringCompleted += handler;
        webclient.DownloadStringAsync(uri);
    });
}

You can then replace the non-error handling DownloadString extension method with:

public static void DownloadString(this Uri uri, Action<string> action)
{
    uri.DownloadString(action, null);
}

And then to use the error handling method you would do this:

var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
    // Do something with `List<Books> books`
}), ex =>
{
    // Do something with `Exception ex`
});

The end result should be fairly simple to use and read. I hope this helps.

君勿笑 2024-12-12 18:26:54

假设您没有编写 ASP.NET 应用程序。

您是否考虑过使用后台工作组件?对于不应该占用 UI 的长时间运行的任务,这是一种获得多线程功能的干净而简单的方法。例如,您可以使用 ProgressChanged 事件和后台工作程序对 UI 执行更新,并且后台工作程序类将确保创建 BW 的线程是执行 ProcessChanged 和 WorkComplete 事件的线程。因此,如果您从 UI 制作了 BW 并使其开始工作,那么您可以从那里安全地更新 UI。

这是来自 MS http://msdn 的快速文章.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

另一个非常好的链接http://www.albahari.com/threading/part3.aspx#_BackgroundWorker

--编辑 -
我查看了该链接,他似乎正在做的是合作取消模式的完整实现。在这里,后台线程将通过例行检查变量并在其为真时取消来优雅地支持取消。 BW 是该模式的实现。

如果你想要一些非常简单的东西,那么你可以尝试只使用 ThreadPool

ThreadPool.QueueUserWorkItem(DoWork);
public void DoWork(){
    //Just remember that this code happens in a seperate thread so don't update 
    //the UI. It will throw an exception. You would need to call 
    //Form.BeginInvoke(UpdateFunction) in order to update the UI 
    DoSomethingInteresting();
}

Assuming you are aren't writing an ASP.NET app.

Have you looked into using a Background Worker component? For long running tasks that shouldn't tie up the UI it is a clean and easy way to get multithreading capabilites. For instance you can perform updates to the UI using the ProgressChanged Event and the background worker and the background worker class will ensure that the thread that created the BW is the one that executes the ProcessChanged and WorkComplete event. So if you made the BW from the UI and set it off to work then you can update the UI safely from there.

Here's a quick article from MS http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx

Another really good link http://www.albahari.com/threading/part3.aspx#_BackgroundWorker

--edit--
I looked at the link and what he appears to be doing is a full implementation of the Cooporative Cancellation pattern. This is where a background thread will support cancellation gracefully by routinely checking a variable and cancelling if it's true. The BW is an implementation of this pattern.

If you want something really simple then you can try just using a ThreadPool

ThreadPool.QueueUserWorkItem(DoWork);
public void DoWork(){
    //Just remember that this code happens in a seperate thread so don't update 
    //the UI. It will throw an exception. You would need to call 
    //Form.BeginInvoke(UpdateFunction) in order to update the UI 
    DoSomethingInteresting();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文