0

In a Form, in the Load event I need to kick off two tasks (as below). And then have it call my form object when either completes. How can I do that?

Or, am I not understanding this right? I did set my load method to be async. So does that mean it returned from the call to Load immediately, but did not complete as I called LoadMetadata with an await? And therefore the Form all processed right, but Load won't execute all code until one of the tasks completes.

Is that it? And so I'm ok. (I may be so used to all the housekeeping I have to do around threads, I'm making this more complicated than it is.)

    private async void LoadingMetadata_Load(object sender, EventArgs e)
    { 
            // load the metadata - this creates a task and returns.
            var result = await LoadMetadata(this, profile, source.Token);
        }



    private static async Task<bool> LoadMetadata(LoadingMetadata dlg, DataSourceProfile profile, CancellationToken cancellationToken)
    {

        // We create a TaskCompletionSource of decimal
        var taskCompletionSource = new TaskCompletionSource<bool>();

        // Registering a lambda into the cancellationToken
        cancellationToken.Register(() =>
        {
            // We received a cancellation message, cancel the TaskCompletionSource.Task
            taskCompletionSource.TrySetCanceled();
        });

        // load the metadata
        Task<bool> task = Task.Run(() => { profile.ReloadMetadata(); return true; } );

        // Wait for the first task to finish among the two
        var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);

        // If the completed task is our long running operation we set its result.
        if (completedTask == task)
        {
            // Extract the result, the task is finished and the await will return immediately
            var result = await task;

            // Set the taskCompletionSource result
            taskCompletionSource.TrySetResult(result);
        }

        // close the dialog
        bool success = await taskCompletionSource.Task;
        dlg.DialogResult = success ? DialogResult.OK : DialogResult.Cancel;
        dlg.Close();

        // Return the result of the TaskCompletionSource.Task
        return success;
    }
David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • Why do you create a `TaskCompletionSource` here? Are you doing this "to support cancellation"? To allow cancelling your work you just have to pass the cancellation token to all child tasks and check its cancellation status inside the work. Also, your understanding of async appears to be incorrect: annotating your method with `async` and then `await`ing the task inside does _not_ make your delegate return immediately. Await will force your code to stop executing at that point and free the thread, but the delegate will only finish once the continuation after the `await` finishes. – julealgon Jan 01 '20 at 20:50
  • @julealgon I have to cancel this way because it's a 3rd party library I'm calling. So I cannot pass a cancellation token in. On the "return immediately" I mis-spoke (this is all new to me). What I meant to say is execution in the method waits at that point - but the execution returns from that method immediately allowing the code that called it to continue. Is that right? This is still weird to me, that a method doesn't complete (at that time), yet still returns. It makes all this simple, but takes a bit of getting used to. It goes against what was normal before. – David Thielen Jan 01 '20 at 22:19
  • When is the cancellation token canceled? – Paulo Morgado Jan 01 '20 at 22:40
  • @PauloMorgado I have a button click handler. When they click the [Cancel] button in a dialog I have displayed while all this is going on, that cancels it. – David Thielen Jan 01 '20 at 23:42
  • I'm not sure asynchronous code is helping you here. Seems like you want to do something with the result of `profile.ReloadMetadata()` if, and only if, the dialog is still open. Why don't you do just that? – Paulo Morgado Jan 02 '20 at 08:35
  • @PauloMorgado The purpose is to have a dialog up where the user can cancel LoadMetadata() while it is running. For slow datasources it can take 30 minutes to complete (not out fault - it's the remote datasource). – David Thielen Jan 02 '20 at 11:47
  • If you don't have a way to kill that task, it will be hanging anyway. If there was a way to cancel it, your code would work. – Paulo Morgado Jan 02 '20 at 12:11
  • `What I meant to say is execution in the method waits at that point - but the execution returns from that method immediately allowing the code that called it to continue. Is that right? This is still weird to me, that a method doesn't complete (at that time), yet still returns.` It does not _return_ after the await, it only frees the original thread that called the method to do something else. If a caller awaits the original call, he will not be able to execute the next statement. Only when the underlying task completes, it will resume the call. Async/await creates a state machine to handle it – julealgon Jan 02 '20 at 21:19
  • Is the object in the `profile` variable the external library class, or do you have any control over that one? To allow proper cancellation would require you to pass the cancellation token into the method that actually performs the work, and then check its cancellation status inside. Tasks use cooperative cancellation, contrary to `Threads` that can be killed. – julealgon Jan 02 '20 at 21:24
  • This might be helpful to you: https://stackoverflow.com/a/44545953/1946412 – julealgon Jan 02 '20 at 21:27

0 Answers0