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

How to use Task<T> raising an event and waiting for event to be finished

我有以下场景:

  1. 正在请求启动网络服务的客户端

    public bool Start(MyProject project, string error)
    
  2. 方法中接收客户端调用的web服务

    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. 正在订阅位于 Web 服务中的事件的应用程序:

    app.Start += EventHandler(App_Start)
    
    private bool App_Start()
    {
       // does something
       returns true/false
    }
    
  4. 我希望网络服务在任务中触发事件,然后等待app.exe中的函数完成,然后return在这里通知用户任务已成功完成。

我不确定该怎么做,但理论上它看起来像这样:

Task<bool> startTask = Task.Factory.StartNew(() => { OnStart() });
startTask.WaitAll(); // I think this is what I would need to for 4.0
return startTask.Result

我希望我的描述足够让别人看到我正在尝试做的事情。我希望该服务不必了解有关客户端的任何信息,只需 运行 任务,一旦事件完成执行,就会回到这一点,并 return 向客户端表示一个布尔值 success/failure.

这可能吗?还是我采取了错误的方法?

更新: 显然 OnStart 不是一个事件,所以我该怎么做你要向我解释的事情?

您可以通过 TaskCompletionSource<T> 将描述基于事件的异步模式的事件包装到 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;
}

所以我想我现在明白需要如何完成这就是我现在正在做的。

服务代码:

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。