【问题标题】:How to use Task<T> raising an event and waiting for event to be finished如何使用 Task<T> 引发事件并等待事件完成
【发布时间】:2015-03-30 20:31:39
【问题描述】:

我有以下场景:

  1. 请求启动 web 服务的客户端

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

    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. 我希望 Web 服务在 Task 中触发事件,然后等待 app.exe 中的函数完成,然后返回此处通知用户任务已成功完成。

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

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 不是一个事件,所以我该如何做你试图向我解释的事情?

【问题讨论】:

    标签: c# multithreading asynchronous event-handling task-parallel-library


    【解决方案1】:

    您可以通过TaskCompletionSource&lt;T&gt; 将描述基于事件的异步模式的事件包装到Task&lt;T&gt; 中。基本模式通常是这样的:

    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 端的代码是否适用于这种情况?
    • 没关系,我刚刚意识到(或者我认为我做到了)这应该在哪里:)
    • 我要编辑我的代码,这样你就可以看到我在做什么,我仍然对如何使用 TaskCompletionSource 有点困惑
    • 这将取决于具体情况,但通常在实现此模式时,在任务完成后移除处理程序很重要。至少,如果事件多次触发,或者触发事件的对象将比此方法长一段时间。
    • @Servy 如何以这样的模式删除处理程序?
    【解决方案2】:

    所以我想我现在明白这需要怎么做,这就是我现在的做法。

    服务代码:

    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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-09
      • 2021-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多