如何使用 Task<T> 触发事件并等待事件完成

3

我有以下情况:

  1. Client who is requesting a webservice to start

    public bool Start(MyProject project, string error)
    
  2. A web service who receives the call from the client in a method

    public event EventHandler<StartEventArgs> startEvent;
    
    public bool Start(MyProject project, string error)
    {
        Task<bool> result = StartAsync(project, error);
    
        return result.Result;
    }
    
    protected virtual void OnStart(StartEventArgs e)
    {
        // thread safe trick, snapshot of event
        var se = startEvent;
        if (se != null)
        {
            startEvent(this, e);
        }
    }
    
    private Task<bool> StartAsync(MyProject project, string error)
    {
        var taskCompletion = new TaskCompletionSource<bool>();
    
        this.startEvent += (p, e) => taskCompletion.TrySetResult((e.Error == string.Empty) ? true : false);
    
        this.OnStart(new StartEventArgs(project, error));
    
        return taskCompletion.Task;
    }
    
  3. An application that is subscribing to an event that is located in the web service:

    app.Start += EventHandler(App_Start)
    
    private bool App_Start()
    {
       // does something
       returns true/false
    }
    
  4. I want the web service to fire off the event in a Task, then wait for the function in the app.exe to finish and then return here to notify the user that the task has completed successfully.

我不确定如何做到这一点,但理论上它应该类似于这样:
Task<bool> startTask = Task.Factory.StartNew(() => { OnStart() });
startTask.WaitAll(); // I think this is what I would need to for 4.0
return startTask.Result

我希望我的描述足够清晰,让人们能够理解我的意图。我希望服务不需要了解客户端的任何信息,只需运行任务,一旦事件完成执行,就返回到这个点,并向客户端返回代表成功/失败的布尔值。
这种方式可行吗?还是我采取了错误的方法?
更新: 显然OnStart不是一个事件,那么我该如何实现你所说的内容?
2个回答

3

您可以通过 TaskCompletionSource<T> 将基于事件的异步模式(Event based asynchronous pattern)描述的事件包装成一个 Task<T>。这一基本模式通常如下所示:

Task<bool> StartAsync()
{
    var tcs = new TaskCompletionSource<bool>();

    // When the event returns, set the result, which "completes" the task
    service.OnStarted += (o,e) => tcs.TrySetResult(e.Success);

    // If an error occurs, error out the task (optional)
    service.OnStartError += (o,e) => tcs.TrySetException(e.Exception);

    // Start the service call
    service.Start();

    // Return the Task<T>
    return tcs.Task;
}

上面的代码是否适用于该场景中的App.exe侧? - ShaffDaddy
没事了,我刚意识到(或者我认为我已经意识到)这应该放在哪里了 :) - ShaffDaddy
我将编辑我的代码,以便您可以看到我想要做什么,我仍然有点困惑如何使用TaskCompletionSource。 - ShaffDaddy
2
这将取决于具体情况,但通常在实现此模式时,重要的是在任务完成后删除处理程序。至少,如果事件会多次触发,或者触发事件的对象将比该方法存在的时间长得多。 - Servy
@Servy 如何在这样的模式下移除处理程序? - axa

1
我认为我现在明白了这应该怎么做,以下是我现在的做法。
服务代码:
public void SetStartTask(Task<bool> startTask)
        {
            this.startTask = startTask;
        }

public bool Start(RtProjectInfo project, string error)
        {
            StartEventArgs args = new StartEventArgs(project, error);

            OnStart(args);

            return startTask.Result;
        }

protected virtual void OnStart(StartEventArgs e)
        {
            // thread safe trick, snapshot of event
            var se = startEvent;

            if (se != null)
            {
                startEvent(this, e);
            }
        }

public void StartFinished(MyProject project, string error)
        {
            OnStartFinish(new StartEventArgs(project, error));
        }

protected virtual void OnStartFinish(StartEventArgs e)
            {
                var sef = startFinished;

                if (sef != null)
                {
                    startFinished(this, e);
                }
            }

这是一个测试客户端实现。
public void Start_Event(object sender, StartEventArgs e)
        {
            Task<bool> startTask = StartAsync();

            service.SetStartTask(startTask);

            DoOtherWork();
            DoOtherWork();
            DoOtherWork();
        }

private Task<bool> StartAsync()
        {
            var taskCompletion = new TaskCompletionSource<bool>();

            service.startFinished += (p, e) => 
            {
                taskCompletion.TrySetResult((e.Error == string.Empty) ? true : false); 
            };

            return taskCompletion.Task;
        }

private void DoingWork()
        {
            for(int i = 0; i < 100; ++i)
            {

            }

            service.StartFinished(project, error);
        }

        private void DoOtherWork()
        {
            for (int i = 0; i < 100000; ++i)
            {                    
            }
        }

当某个客户端调用DoingWork()方法时,显然将会接收到事件,这样每个人都会很高兴!如果有更好的建议,请提供,因为我正在学习如何正确使用TPL。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接