简体   繁体   中英

Is there a way to signal a System.Threading.Tasks.Task complete?

I have a library that is a complicated arbiter of many network connections. Each method of it's primary object takes a delegate, which is called when the network responds to a given request.

I want to translate my library to use the new .NET 4.5 "async/await" pattern; this would require me to return a "Task" object, which would signal to the user that the asynchronous part of the call is complete. Creating this object requires a function for the task to represent - As far as my understanding, it is essentially a lightweight thread.

This doesn't really fit the design of my library - I would like the task to behave more like an event, and directly signal to the user that their request has completed, rather then representing a function. Is this possible? Should i avoid abusing the "async/await" pattern in this way?

I don't know if I'm wording this very well, I hope you understand my meaning. Thank you for any help.

As far as my understanding, it is essentially a lightweight thread.

No, that's not really true. I can be true, under certain circumstances, but that's just one usage of Task . You can start a thread by passing it a delegate, and having it execute it (usually asynchronously, possibly synchronously, and by default using the thread pool).

Another way of using threads is through the use of a TaskCompletionSource . When you do that the task is (potentially) not creating any threads, using the thread pool, or anything along those lines. One common usage of this model is converting an event-based API to a Task-based API:

Let's assume, just because it's a common example, that we want to have a Task that will be completed when a From that we have is closed. There is already a FormClosed event that fires when that event occurs:

public static Task WhenClosed(this Form form)
{
    var tcs = new TaskCompletionSource<object>();

    form.FormClosing += (_, args) =>
    {
        tcs.SetResult(null);
    };

    return tcs.Task;
}

We create a TaskCompletionSource , add a handler to the event in question, in that handler we signal the completion of the task, and the TaskCompletionSource provides us with a Task to return to the caller. This Task will not have resulted in any new threads being created, it won't use the thread pool, or anything like that.

You can have a Task/Event based model using this construct that appears quite asynchronous, but only using a single thread to do all work (the UI thread).

In general, anytime you want to have a Task that represents something other than the execution of a function you'll want to consider using a TaskCompletionSource . It's usually the appropriate conceptual way to approach the problem, other than possibly using one of the existing TPL methods, such as WhenAll , WhenAny , etc.

Should I avoid abusing the "async/await" pattern in this way?

No, because it's not abuse. It's a perfectly appropriate use of Task constructs, as well as async/await . Consider, for example, the code that you can write using the helper method that I have above:

private async void button1_Click(object sender, EventArgs e)
{
    Form2 popup = new Form2();
    this.Hide();
    popup.Show();
    await popup.WhenClosed();
    this.Show();
}

This code now works just like it reads; create a new form, hide myself, show the popup, wait until the popup is closed, and then show myself again. However, since it's not a blocking wait the UI thread isn't blocked. We also don't need to bother with events; adding handlers, dealing with multiple contexts; moving our logic around, or any of it. (All of that happens, it's just hidden from us.)

I want to translate my library to use the new .NET 4.5 "async/await" pattern; this would require me to return a "Task" object, which would signal to the user that the asynchronous part of the call is complete.

Well, not really - you can return anything which implements the awaitable pattern - but a Task is the simplest way of doing this.

This doesn't really fit the design of my library - I would like the task to behave more like an event, and directly signal to the user that their request has completed, rather then representing a function.

You can call Task.ContinueWith to act as a "handler" to execute when the task completes. Indeed, that's what TaskAwaiter does under the hood.

Your question isn't terribly clear to be honest, but if you really want to create a Task which you can then force to completion whenever you like, I suspect you just want TaskCompletionSource<TResult> - you call the SetResult , SetCanceled or SetException methods to indicate the appropriate kind of completion. (Or you can call the TrySet... versions.) Use the Task property to return a task to whatever needs it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM