新的 C# 异步功能有什么好的非联网示例?

发布于 2024-09-30 08:50:12 字数 447 浏览 5 评论 0原文

Microsoft 刚刚发布了新的 C# 异步功能。到目前为止,我见过的每个示例都是关于从 HTTP 异步下载内容的。当然还有其他重要的异步事情吗?

假设我没有编写新的 RSS 客户端或 Twitter 应用程序。 C# Async 对我来说有什么有趣的地方?

编辑我顿悟了!观看Anders 的 PDC 会议时的时刻。过去我曾开发过使用“观察者”线程的程序。这些线程等待某些事情发生,例如监视文件更改。它们不做工作,只是闲置,当有事情发生时通知主线程。在新模型中,这些线程可以替换为等待/异步代码。

Microsoft just announced the new C# Async feature. Every example I've seen so far is about asynchronously downloading something from HTTP. Surely there are other important async things?

Suppose I'm not writing a new RSS client or Twitter app. What's interesting about C# Async for me?

Edit I had an Aha! moment while watching Anders' PDC session. In the past I have worked on programs that used "watcher" threads. These threads sit waiting for something to happen, like watching for a file to change. They aren't doing work, they're just idle, and notify the main thread when something happens. These threads could be replaced with await/async code in the new model.

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

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

发布评论

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

评论(8

染火枫林 2024-10-07 08:50:13

哦,这听起来很有趣。我还没有玩过 CTP,只是查看了白皮书。在看到 Anders Hejlsberg 的演讲之后,我想我可以明白它如何被证明是有用的。

据我了解,异步使得编写异步调用更容易阅读和实现。同样,现在编写迭代器更容易(而不是手动编写功能)。这是必不可少的阻塞进程,因为在解除阻塞之前无法完成任何有用的工作。如果您正在下载一个文件,则在获得该文件之前您无法做任何有用的事情,从而浪费线程。考虑如何调用一个您知道会阻塞不确定长度并返回一些结果的函数,然后对其进行处理(例如,将结果存储在文件中)。你会怎么写呢?这是一个简单的例子:

static object DoSomeBlockingOperation(object args)
{
    // block for 5 minutes
    Thread.Sleep(5 * 60 * 1000);

    return args;
}

static void ProcessTheResult(object result)
{
    Console.WriteLine(result);
}

static void CalculateAndProcess(object args)
{
    // let's calculate! (synchronously)
    object result = DoSomeBlockingOperation(args);

    // let's process!
    ProcessTheResult(result);
}

好的,我们已经实现了。但是等等,计算需要几分钟才能完成。如果我们想要一个交互式应用程序并在计算时执行其他操作(例如渲染 UI)怎么办?这不好,因为我们同步调用该函数,并且由于线程正在等待解除阻塞,我们必须等待它完成有效冻结应用程序。

答,异步调用函数昂贵的函数。这样我们就不必等待阻塞操作完成。但我们该怎么做呢?我们将异步调用该函数,并注册一个回调函数,以便在解除阻塞时调用,以便我们可以处理结果。

static void CalculateAndProcessAsyncOld(object args)
{
    // obtain a delegate to call asynchronously
    Func<object, object> calculate = DoSomeBlockingOperation;

    // define the callback when the call completes so we can process afterwards
    AsyncCallback cb = ar =>
        {
            Func<object, object> calc = (Func<object, object>)ar.AsyncState;
            object result = calc.EndInvoke(ar);

            // let's process!
            ProcessTheResult(result);
        };

    // let's calculate! (asynchronously)
    calculate.BeginInvoke(args, cb, calculate);
}
  • 注意:当然,我们可以启动另一个线程来执行此操作,但这意味着我们要生成一个线程,该线程只是坐在那里等待解锁,然后做一些有用的工作。那将是一种浪费。

现在调用是异步的,我们不必担心等待计算完成和处理,它是异步完成的。它会尽可能完成。除了直接异步调用代码之外,您还可以使用任务:

static void CalculateAndProcessAsyncTask(object args)
{
    // create a task
    Task<object> task = new Task<object>(DoSomeBlockingOperation, args);

    // define the callback when the call completes so we can process afterwards
    task.ContinueWith(t =>
        {
            // let's process!
            ProcessTheResult(t.Result);
        });

    // let's calculate! (asynchronously)
    task.Start();
}

现在我们异步调用我们的函数。但怎样才能做到这一点呢?首先,我们需要委托/任务能够异步调用它,我们需要一个回调函数来处理结果,然后调用该函数。我们已经将两行函数调用转变为更多的异步调用。不仅如此,代码中的逻辑已经变得比以前或可能的更加复杂。尽管使用任务有助于简化流程,但我们仍然需要做一些事情来实现它。我们只想异步运行然后处理结果。为什么我们不能这样做呢?现在我们可以:

// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
    //it is my understanding that async will take this method and convert it to a task automatically
    return DoSomeBlockingOperation(args);
}

static async void CalculateAndProcessAsyncNew(object args)
{
    // let's calculate! (asynchronously)
    object result = await DoSomeBlockingOperationAsync(args);

    // let's process!
    ProcessTheResult(result);
}

这是一个非常简化的示例,具有简单的操作(计算、处理)。想象一下,如果每个操作不能方便地放入单独的函数中,而是有数百行代码。为了获得异步调用的好处,这会增加很多复杂性。


白皮书中使用的另一个实际示例是在 UI 应用程序上使用它。修改为使用上面的示例:

private async void doCalculation_Click(object sender, RoutedEventArgs e) {
    doCalculation.IsEnabled = false;
    await DoSomeBlockingOperationAsync(GetArgs());
    doCalculation.IsEnabled = true;
}

如果您完成了任何 UI 编程(无论是 WinForms 还是 WPF)并尝试在处理程序中调用昂贵的函数,您就会知道这很方便。为此使用后台工作线程不会有太大帮助,因为后台线程将坐在那里等待直到它可以工作。


假设您有办法控制某些外部设备,比如说打印机。您想在发生故障后重新启动设备。当然,打印机启动并准备好运行需要一些时间。您可能必须考虑到重新启动没有帮助并尝试再次重新启动。你别无选择,只能等待。如果您是异步执行的,则不会。

static async void RestartPrinter()
{
    Printer printer = GetPrinter();
    do
    {
        printer.Restart();

        printer = await printer.WaitUntilReadyAsync();

    } while (printer.HasFailed);
}

想象一下编写没有异步的循环。


我有最后一个例子。想象一下,如果您必须在一个函数中执行多个阻塞操作并且想要异步调用。你想要什么?

static void DoOperationsAsyncOld()
{
    Task op1 = new Task(DoOperation1Async);
    op1.ContinueWith(t1 =>
    {
        Task op2 = new Task(DoOperation2Async);
        op2.ContinueWith(t2 =>
        {
            Task op3 = new Task(DoOperation3Async);
            op3.ContinueWith(t3 =>
            {
                DoQuickOperation();
            }
            op3.Start();
        }
        op2.Start();
    }
    op1.Start();
}

static async void DoOperationsAsyncNew()
{
    await DoOperation1Async();

    await DoOperation2Async();

    await DoOperation3Async();

    DoQuickOperation();
}

阅读白皮书,它实际上有很多实际示例,例如编写并行任务等。

我迫不及待地想在 CTP 中或 .NET 5.0 最终发布时开始使用它。

Ooh, this sounds interesting. I'm not playing with the CTP just yet, just reviewing the whitepaper. After seeing Anders Hejlsberg's talk about it, I think I can see how it could prove useful.

As I understand, async makes writing asynchronous calls easier to read and implement. Very much in the same way writing iterators is easier right now (as opposed to writing out the functionality by hand). This is essential blocking processes since no useful work can be done, until it is unblocked. If you were downloading a file, you cannot do anything useful until you get that file letting the thread go to waste. Consider how one would call a function which you know will block for an undetermined length and returns some result, then process it (e.g., store the results in a file). How would you write that? Here's a simple example:

static object DoSomeBlockingOperation(object args)
{
    // block for 5 minutes
    Thread.Sleep(5 * 60 * 1000);

    return args;
}

static void ProcessTheResult(object result)
{
    Console.WriteLine(result);
}

static void CalculateAndProcess(object args)
{
    // let's calculate! (synchronously)
    object result = DoSomeBlockingOperation(args);

    // let's process!
    ProcessTheResult(result);
}

Ok good, we have it implemented. But wait, the calculation takes minutes to complete. What if we wanted to have an interactive application and do other things while the calculation took place (such as rendering the UI)? This is no good, since we called the function synchronously and we have to wait for it to finish effectively freezing the application since the thread is waiting to be unblocked.

Answer, call the function expensive function asynchronously. That way we're not bound to waiting for the blocking operation to complete. But how do we do that? We'd call the function asynchronously and register a callback function to be called when unblocked so we may process the result.

static void CalculateAndProcessAsyncOld(object args)
{
    // obtain a delegate to call asynchronously
    Func<object, object> calculate = DoSomeBlockingOperation;

    // define the callback when the call completes so we can process afterwards
    AsyncCallback cb = ar =>
        {
            Func<object, object> calc = (Func<object, object>)ar.AsyncState;
            object result = calc.EndInvoke(ar);

            // let's process!
            ProcessTheResult(result);
        };

    // let's calculate! (asynchronously)
    calculate.BeginInvoke(args, cb, calculate);
}
  • Note: Sure we could start another thread to do this but that would mean we're spawning a thread that just sits there waiting to be unblocked, then do some useful work. That would be a waste.

Now the call is asynchronous and we don't have to worry about waiting for the calculation to finish and process, it's done asynchronously. It will finish when it can. An alternative to calling code asynchronously directly, you could use a Task:

static void CalculateAndProcessAsyncTask(object args)
{
    // create a task
    Task<object> task = new Task<object>(DoSomeBlockingOperation, args);

    // define the callback when the call completes so we can process afterwards
    task.ContinueWith(t =>
        {
            // let's process!
            ProcessTheResult(t.Result);
        });

    // let's calculate! (asynchronously)
    task.Start();
}

Now we called our function asynchronously. But what did it take to get it that way? First of all, we needed the delegate/task to be able to call it asynchronously, we needed a callback function to be able to process the results, then call the function. We've turned a two line function call to much more just to call something asynchronously. Not only that, the logic in the code has gotten more complex then it was or could be. Although using a task helped simplify the process, we still needed to do stuff to make it happen. We just want to run asynchronously then process the result. Why can't we just do that? Well now we can:

// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
    //it is my understanding that async will take this method and convert it to a task automatically
    return DoSomeBlockingOperation(args);
}

static async void CalculateAndProcessAsyncNew(object args)
{
    // let's calculate! (asynchronously)
    object result = await DoSomeBlockingOperationAsync(args);

    // let's process!
    ProcessTheResult(result);
}

Now this was a very simplified example with simple operations (calculate, process). Imagine if each operation couldn't conveniently be put into a separate function but instead have hundreds of lines of code. That's a lot of added complexity just to gain the benefit of asynchronous calling.


Another practical example used in the whitepaper is using it on UI apps. Modified to use the above example:

private async void doCalculation_Click(object sender, RoutedEventArgs e) {
    doCalculation.IsEnabled = false;
    await DoSomeBlockingOperationAsync(GetArgs());
    doCalculation.IsEnabled = true;
}

If you've done any UI programming (be it WinForms or WPF) and attempted to call an expensive function within a handler, you'll know this is handy. Using a background worker for this wouldn't be that much helpful since the background thread will be sitting there waiting until it can work.


Suppose you had a way to control some external device, let's say a printer. And you wanted to restart the device after a failure. Naturally it will take some time for the printer to start up and be ready for operation. You might have to account for the restart not helping and attempt to restart again. You have no choice but to wait for it. Not if you did it asynchronously.

static async void RestartPrinter()
{
    Printer printer = GetPrinter();
    do
    {
        printer.Restart();

        printer = await printer.WaitUntilReadyAsync();

    } while (printer.HasFailed);
}

Imagine writing the loop without async.


One last example I have. Imagine if you had to do multiple blocking operations in a function and wanted to call asynchronously. What would you prefer?

static void DoOperationsAsyncOld()
{
    Task op1 = new Task(DoOperation1Async);
    op1.ContinueWith(t1 =>
    {
        Task op2 = new Task(DoOperation2Async);
        op2.ContinueWith(t2 =>
        {
            Task op3 = new Task(DoOperation3Async);
            op3.ContinueWith(t3 =>
            {
                DoQuickOperation();
            }
            op3.Start();
        }
        op2.Start();
    }
    op1.Start();
}

static async void DoOperationsAsyncNew()
{
    await DoOperation1Async();

    await DoOperation2Async();

    await DoOperation3Async();

    DoQuickOperation();
}

Read the whitepaper, it actually has a lot of practical examples like writing parallel tasks and others.

I can't wait to start playing with this either in the CTP or when .NET 5.0 finally makes it out.

数理化全能战士 2024-10-07 08:50:13

主要场景是任何涉及高延迟的场景。也就是说,“要求结果”和“获得结果”之间有很多时间。网络请求是高延迟场景最明显的示例,紧随其后的是一般 I/O,然后是受另一个内核的 CPU 限制的冗长计算。

然而,这项技术可能还可以很好地适应其他场景。例如,考虑编写 FPS 游戏的逻辑脚本。假设您有一个按钮单击事件处理程序。当玩家点击按钮时要播放警笛两秒钟来警告敌人,然后打开门十秒钟。说这样的话不是很好吗:

button.Disable();
await siren.Activate(); 
await Delay(2000);
await siren.Deactivate();
await door.Open();
await Delay(10000);
await door.Close();
await Delay(1000);
button.Enable();

每个任务都在 UI 线程上排队,因此没有任何阻塞,并且每个任务在其工作完成后在正确的位置恢复点击处理程序。

The main scenarios are any scenario that involves high latency. That is, lots of time between "ask for a result" and "obtain a result". Network requests are the most obvious example of high latency scenarios, followed closely by I/O in general, and then by lengthy computations that are CPU bound on another core.

However, there are potentially other scenarios that this technology will mesh nicely with. For example, consider scripting the logic of a FPS game. Suppose you have a button click event handler. When the player clicks the button you want to play a siren for two seconds to alert the enemies, and then open the door for ten seconds. Wouldn't it be nice to say something like:

button.Disable();
await siren.Activate(); 
await Delay(2000);
await siren.Deactivate();
await door.Open();
await Delay(10000);
await door.Close();
await Delay(1000);
button.Enable();

Each task gets queued up on the UI thread, so nothing blocks, and each one resumes the click handler at the right point after its job is finished.

未央 2024-10-07 08:50:13

今天我发现了另一个很好的用例:您可以等待用户交互。

例如,如果一个表单有一个打开另一个表单的按钮:

Form toolWindow;
async void button_Click(object sender, EventArgs e) {
  if (toolWindow != null) {
     toolWindow.Focus();
  } else {
     toolWindow = new Form();
     toolWindow.Show();
     await toolWindow.OnClosed();
     toolWindow = null;
  }
}

当然,这并不比

toolWindow.Closed += delegate { toolWindow = null; }

But 我认为它很好地演示了 await 可以做什么。一旦事件处理程序中的代码很重要,await 就会使编程变得更加容易。想象一下用户必须单击一系列按钮:

async void ButtonSeries()
{
  for (int i = 0; i < 10; i++) {
    Button b = new Button();
    b.Text = i.ToString();
    this.Controls.Add(b);
    await b.OnClick();
    this.Controls.Remove(b);
  }
}

当然,您可以使用普通的事件处理程序来执行此操作,但这需要您分解循环并将其转换为更难以理解的内容。

请记住,await 可以与将来某个时刻完成的任何事情一起使用。下面是实现上述工作的扩展方法 Button.OnClick():

public static AwaitableEvent OnClick(this Button button)
{
    return new AwaitableEvent(h => button.Click += h, h => button.Click -= h);
}
sealed class AwaitableEvent
{
    Action<EventHandler> register, deregister;
    public AwaitableEvent(Action<EventHandler> register, Action<EventHandler> deregister)
    {
        this.register = register;
        this.deregister = deregister;
    }
    public EventAwaiter GetAwaiter()
    {
        return new EventAwaiter(this);
    }
}
sealed class EventAwaiter
{
    AwaitableEvent e;
    public EventAwaiter(AwaitableEvent e) { this.e = e; }

    Action callback;

    public bool BeginAwait(Action callback)
    {
        this.callback = callback;
        e.register(Handler);
        return true;
    }
    public void Handler(object sender, EventArgs e)
    {
        callback();
    }
    public void EndAwait()
    {
        e.deregister(Handler);
    }
}

不幸的是,似乎不可能将 GetAwaiter() 方法直接添加到 EventHandler (允许 EventHandler >await button.Click;),因为这样该方法将不知道如何注册/取消注册该事件。
它有点样板,但 AwaitableEvent 类可以重用于所有事件(不仅仅是 UI)。通过稍作修改并添加一些泛型,您可以允许检索 EventArgs:

MouseEventArgs e = await button.OnMouseDown();

我可以看到这对于一些更复杂的 UI 手势(拖放、鼠标手势等)很有用 - 尽管您必须这样做添加对取消当前手势的支持。

I've found another nice use-case for this today: you can await user interaction.

For example, if one form has a button that opens another form:

Form toolWindow;
async void button_Click(object sender, EventArgs e) {
  if (toolWindow != null) {
     toolWindow.Focus();
  } else {
     toolWindow = new Form();
     toolWindow.Show();
     await toolWindow.OnClosed();
     toolWindow = null;
  }
}

Granted, this isn't really any simpler than

toolWindow.Closed += delegate { toolWindow = null; }

But I think it nicely demonstrates what await can do. And once the code in the event handler is non-trivial, await make programming much easier. Think about the user having to click a sequence of buttons:

async void ButtonSeries()
{
  for (int i = 0; i < 10; i++) {
    Button b = new Button();
    b.Text = i.ToString();
    this.Controls.Add(b);
    await b.OnClick();
    this.Controls.Remove(b);
  }
}

Sure, you could do this with normal event handlers, but it would require you to take apart the loop and convert it into something much harder to understand.

Remember that await can be used with anything that gets completed at some point in the future. Here's the extension method Button.OnClick() to make the above work:

public static AwaitableEvent OnClick(this Button button)
{
    return new AwaitableEvent(h => button.Click += h, h => button.Click -= h);
}
sealed class AwaitableEvent
{
    Action<EventHandler> register, deregister;
    public AwaitableEvent(Action<EventHandler> register, Action<EventHandler> deregister)
    {
        this.register = register;
        this.deregister = deregister;
    }
    public EventAwaiter GetAwaiter()
    {
        return new EventAwaiter(this);
    }
}
sealed class EventAwaiter
{
    AwaitableEvent e;
    public EventAwaiter(AwaitableEvent e) { this.e = e; }

    Action callback;

    public bool BeginAwait(Action callback)
    {
        this.callback = callback;
        e.register(Handler);
        return true;
    }
    public void Handler(object sender, EventArgs e)
    {
        callback();
    }
    public void EndAwait()
    {
        e.deregister(Handler);
    }
}

Unfortunately it doesn't seem possible to add the GetAwaiter() method directly to EventHandler (allowing await button.Click;) because then the method wouldn't know how to register/deregister that event.
It's a bit of boilerplate, but the AwaitableEvent class can be re-used for all events (not just UI). And with a minor modification and adding some generics, you could allow retrieving the EventArgs:

MouseEventArgs e = await button.OnMouseDown();

I could see this being useful with some more complex UI gestures (drag'n'drop, mouse gestures, ...) - though you'd have to add support for cancelling the current gesture.

维持三分热 2024-10-07 08:50:13

CTP中有一些示例和演示不使用网络,甚至有些不进行任何I/O。

它确实适用于所有多线程/并行问题领域(已经存在)。

Async 和 Await 是构建所有并行代码的一种新的(更简单的)方法,无论是 CPU 限制还是 I/O 限制。最大的改进是在 C#5 之前必须使用 APM (IAsyncResult) 模型或事件模型(BackgroundWorker、WebClient)的领域。我认为这就是为什么这些例子现在引领潮流。

There are some samples and demos in the CTP that don't use the Net, and even some that don't do any I/O.

And it does apply to all multithreaded / parallel problem areas (that already exist).

Async and Await are a new (easier) way of structuring all parallel code, be it CPU-bound or I/O bound. The biggest improvement is in areas where before C#5 you had to use the APM (IAsyncResult) model, or the event model (BackgroundWorker, WebClient). I think that is why those examples lead the parade now.

夜无邪 2024-10-07 08:50:13

GUI 时钟就是一个很好的例子;假设你想画一个时钟,它每秒更新显示的时间。从概念上讲,您想要编写

 while true do
    sleep for 1 second
    display the new time on the clock

并使用 await (或使用 F# async)来异步睡眠,您可以编写此代码以非阻塞方式在 UI 线程上运行。

http://lorgonblog.wordpress.com/ 2010/03/27/f-async-on-the-client-side/

A GUI clock is a good example; say you want to draw a clock, that updates the time shown every second. Conceptually, you want to write

 while true do
    sleep for 1 second
    display the new time on the clock

and with await (or with F# async) to asynchronously sleep, you can write this code to run on the UI thread in a non-blocking fashion.

http://lorgonblog.wordpress.com/2010/03/27/f-async-on-the-client-side/

弃爱 2024-10-07 08:50:13

当您进行异步操作时,async 扩展在某些情况下很有用。异步操作有明确的开始完成。< /em> 当异步操作完成时,它们可能会产生结果或错误。 (取消被视为一种特殊类型的错误)。

异步操作在三种情况下很有用(广义而言):

  • 保持 UI 响应。每当您有长时间运行的操作(无论是 CPU 密集型还是 I/O 密集型)时,请将其设为异步。
  • 扩展您的服务器。在服务器端明智地使用异步操作可能有助于您的服务器扩展。例如,异步 ASP.NET 页面可以使用async 操作。但是,这是 并不总是胜利;您需要首先评估您的可扩展性瓶颈。
  • 在库或共享代码中提供干净的异步 API。 async 非常适合可重用性。

当您开始采用异步方式做事时,您会发现第三种情况变得更加常见。 async 代码与其他 async 代码配合得最好,因此异步代码在代码库中“增长”。

有几种类型的并发,其中异步不是最好的工具:

  • 并行化。并行算法可以使用许多核心(CPU、GPU、计算机)来更快地解决问题。
  • 异步事件。异步事件始终发生,与您的程序无关。他们常常没有“完成”。通常,您的程序将订阅异步事件流,接收一定数量的更新,然后取消订阅。您的程序可以将订阅取消订阅视为“开始”和“完成”,但实际的事件流永远不会真正停止。

并行操作最好使用 PLINQ 或 Parallel 来表达,因为它们对分区、有限并发等有很多内置支持。通过从ThreadPool 线程 (Task.Factory.StartNew)。

异步事件不能很好地映射到异步操作。一个问题是异步操作在完成时只有一个结果。异步事件可以有任意数量的更新。 Rx 是处理异步事件的自然语言。

有一些从 Rx 事件流到异步操作的映射,但它们都不适合所有情况。通过 Rx 来使用异步操作更为自然,而不是相反。 IMO,解决此问题的最佳方法是在库和较低级别代码中尽可能使用异步操作,如果您在某个时候需要 Rx,请从那里开始使用 Rx。

The async extensions are useful in some cases when you have an asynchronous operation. An asynchronous operation has a definite start and completion. When asynchronous operations complete, they may have a result or an error. (Cancellation is treated as a special kind of error).

Asynchronous operations are useful in three situations (broadly speaking):

  • Keeping your UI responsive. Any time you have a long-running operation (whether CPU-bound or I/O-bound), make it asynchronous.
  • Scaling your servers. Using asynchronous operations judiciously on the server side may help your severs to scale. e.g., asynchronous ASP.NET pages may make use of async operations. However, this is not always a win; you need to evaluate your scalability bottlenecks first.
  • Providing a clean asynchronous API in a library or shared code. async is excellent for reusability.

As you begin to adopt the async way of doing things, you'll find the third situation becoming more common. async code works best with other async code, so asynchronous code kind of "grows" through the codebase.

There are a couple of types of concurrency where async is not the best tool:

  • Parallelization. A parallel algorithm may use many cores (CPUs, GPUs, computers) to solve a problem more quickly.
  • Asynchronous events. Asynchronous events happen all the time, independent of your program. They often do not have a "completion." Normally, your program will subscribe to an asynchronous event stream, receive some number of updates, and then unsubscribe. Your program can treat the subscribe and unsubscribe as a "start" and "completion", but the actual event stream never really stops.

Parallel operations are best expressed using PLINQ or Parallel, since they have a lot of built-in support for partitioning, limited concurrency, etc. A parallel operation may easily be wrapped in an awaitable by running it from a ThreadPool thread (Task.Factory.StartNew).

Asynchronous events do not map well to asynchronous operations. One problem is that an asynchronous operation has a single result at its point of completion. Asynchronous events may have any number of updates. Rx is the natural language for dealing with asynchronous events.

There are some mappings from an Rx event stream to an asynchronous operation, but none of them are ideal for all situations. It's more natural to consume asynchronous operations by Rx, rather than the other way around. IMO, the best way of approaching this is to use asynchronous operations in your libraries and lower-level code as much as possible, and if you need Rx at some point, then use Rx from there on up.

深者入戏 2024-10-07 08:50:13

这可能是一个很好的例子,说明如何不使用新的异步功能(不是编写新的 RSS 客户端或 Twitter 应用程序)、虚拟方法调用中的中间方法重载点。老实说,我不确定是否有任何方法可以为每个方法创建多个过载点。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace AsyncText
{
    class Program
    {
        static void Main(string[] args)
        {
            Derived d = new Derived();

            TaskEx.Run(() => d.DoStuff()).Wait();

            System.Console.Read();
        }
        public class Base
        {
            protected string SomeData { get; set; }

            protected async Task DeferProcessing()
            {
                await TaskEx.Run(() => Thread.Sleep(1) );
                return;
            }
            public async virtual Task DoStuff() {
                Console.WriteLine("Begin Base");
                Console.WriteLine(SomeData);
                await DeferProcessing();
                Console.WriteLine("End Base");
                Console.WriteLine(SomeData);
            }
        }
        public class Derived : Base
        {
            public async override Task DoStuff()
            {
                Console.WriteLine("Begin Derived");
                SomeData = "Hello";
                var x = base.DoStuff();
                SomeData = "World";
                Console.WriteLine("Mid 1 Derived");
                await x;
                Console.WriteLine("EndDerived");
            }
        }
    }
}

输出是:

开始导出

开始基地

你好

Mid 1 派生

结束基地

世界

结束派生

使用某些继承层次结构(即使用命令模式),我发现自己偶尔想做这样的事情。

Here is probably a good example of how not to use the new async feature (that's not writing a new RSS client or Twitter app), mid-method overload points in a virtual method call. To be honest, i am not sure there is any way to create more than a single overload point per method.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace AsyncText
{
    class Program
    {
        static void Main(string[] args)
        {
            Derived d = new Derived();

            TaskEx.Run(() => d.DoStuff()).Wait();

            System.Console.Read();
        }
        public class Base
        {
            protected string SomeData { get; set; }

            protected async Task DeferProcessing()
            {
                await TaskEx.Run(() => Thread.Sleep(1) );
                return;
            }
            public async virtual Task DoStuff() {
                Console.WriteLine("Begin Base");
                Console.WriteLine(SomeData);
                await DeferProcessing();
                Console.WriteLine("End Base");
                Console.WriteLine(SomeData);
            }
        }
        public class Derived : Base
        {
            public async override Task DoStuff()
            {
                Console.WriteLine("Begin Derived");
                SomeData = "Hello";
                var x = base.DoStuff();
                SomeData = "World";
                Console.WriteLine("Mid 1 Derived");
                await x;
                Console.WriteLine("EndDerived");
            }
        }
    }
}

Output Is:

Begin Derived

Begin Base

Hello

Mid 1 Derived

End Base

World

EndDerived

With certain inheritance hierarchies (namely using command pattern) i find myself wanting to do stuff like this occasionally.

将军与妓 2024-10-07 08:50:13

这是一篇文章,介绍如何在涉及 UI 和多个操作的非网络场景。

here is an article about showing how to use the 'async' syntax in a non-networked scenario that involves UI and multiple actions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文