【问题标题】:Best way to handle exception in multi task to call WCF operation在多任务中处理异常以调用 WCF 操作的最佳方法
【发布时间】:2017-02-07 13:35:29
【问题描述】:

我已经实现了一个服务名称ExamClient,它有两个操作,一个是Ping,它返回一个基本的string,这意味着服务可用,一个是FindStudy,它在数据库中搜索可能需要很长时间继续。

在另一边,我有几个ExamClient 端点,我想按任务每个端点运行FindStudy,所以在Dispatcher 中我有这样的东西:

public FindStudies_DTO_OUT FindStudies(FindStudies_DTO_IN findStudies_DTO_IN)
{
    List<Study_C> ret = new List<Study_C>(); 
    List<Task> tasks = new List<Task>();
    foreach (var sp in Cluster)
    {
        string serviceAddress = sp.GetLibraryAddress(ServiceLibrary_C.PCM) + "/Exam.svc";
        var task = Task.Run(() =>
        {
            ExamClient examClient = new ExamClient(serviceAddress.GetBinding(), new EndpointAddress(serviceAddress), Token);
            var ping = Task.Run(() =>
            {
                examClient.Ping();
            }); 
            if (!ping.Wait(examClient.Endpoint.Binding.OpenTimeout))
            {
                Logging.Log(LoggingMode.Warning, "Timeout on FindStudies for:{0}, address:{1}", sp.Name, serviceAddress);
                return new List<Study_C>(); // if return null then need to manage it on ret.AddRange(t.Result);
            }  
            return (examClient.FindStudies(findStudies_DTO_IN).Studies.Select(x =>
            {
                x.StudyInstanceUID = string.Format("{0}|{1}", sp.Name, x.StudyInstanceUID);
                x.InstitutionName = sp.Name;
                return x;
            }));
        });
        task.ContinueWith(t =>
        {
            lock (ret)
            {
                ret.AddRange(t.Result);
            }
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
        task.ContinueWith(t =>
        {
            Logging.Log(LoggingMode.Error, "FindStudies failed for :{0}, address:{1}, EXP:{2}", sp.Name, serviceAddress, t.Exception.ToString());
        }, TaskContinuationOptions.OnlyOnFaulted);

        tasks.Add(task);
    } 
    try
    {
        Task.WaitAll(tasks.ToArray());
    }
    catch (AggregateException aggEx)
    {
        foreach (Exception exp in aggEx.InnerExceptions)
        {
            Logging.Log(LoggingMode.Error, "Error while FindStudies EXP:{0}", exp.ToString());
        }
    }

    return new FindStudies_DTO_OUT(ret.Sort(findStudies_DTO_IN.SortColumnName, findStudies_DTO_IN.SortOrderBy));
}

首先我必须对每个端点运行Ping 才能知道连接已建立 之后FindStudy.

如果Cluster 中有 3 个结束点,则 6 个任务以并行模式运行,Ping 为 3 个,FindStudy 为 3 个。

我认为我处理异常的代码有问题... 那么实现这个场景的最佳方法是什么?

提前致谢。

【问题讨论】:

  • 你不应该在你的任务中使用Wait()来访问超时逻辑......查看答案here
  • 查看 cmets 以及有关取消令牌的内容。这真的很重要!
  • 我想我应该等待,因为如果端点可用,则打开超时为 3 秒Ping 在 3 秒内得到响应。
  • 不,使用Wait() 是非常糟糕的做法,可能会导致死锁。取消任务的正确方法是通过取消令牌,它们也有超时。 Ping() 可以接受取消令牌作为参数。
  • 我认为你误会了我。您可以设置 3 秒的超时时间,但您的实现存在缺陷。

标签: c# multithreading task-parallel-library


【解决方案1】:

让我抛出我的答案来简化和删除不必要的代码块。以及代码中的一些解释。

public FindStudies_DTO_OUT FindStudies(FindStudies_DTO_IN findStudies_DTO_IN)
{
    // Thread-safe collection
    var ret = new ConcurrentBag<Study_C>()

    // Loop cluster list and process each item in parallel and wait all process to finish. This handle the parallism better than task run
    Parallel.Foreach(Cluster, (sp) =>
    {
        var serviceAddress = sp.GetLibraryAddress(ServiceLibrary_C.PCM) + "/Exam.svc";

        ExamClient examClient = new ExamClient(serviceAddress.GetBinding(), new EndpointAddress(serviceAddress), Token);

        try
        {
            examClient.Ping();              
            // declare result variable type outside try catch to be visible below               
            var result = examClient.FindStudies(findStudies_DTO_IN);
        }
        catch(TimeoutException timeoutEx)
        {
            // abort examclient to dispose channel properly
            Logging.Log(LoggingMode.Warning, "Timeout on FindStudies for:{0}, address:{1}", sp.Name, serviceAddress);
        }
        catch(FaultException fault)
        {
            Logging.Log(LoggingMode.Error, "FindStudies failed for :{0}, address:{1}, EXP:{2}", sp.Name, serviceAddress, fault.Exception.ToString());
        }
        catch(Exception ex)
        {
            // anything else
        }
        // add exception type as needed for proper logging

        // use inverted if to reduce nested condition
        if( result == null )
            return null;

        var study_c = result.Studies.Select(x =>
        {
            x.StudyInstanceUID = string.Format("{0}|{1}", sp.Name, x.StudyInstanceUID);
            x.InstitutionName = sp.Name;
            return x;
        }));

        // Thread-safe collection
        ret.AddRange(study_c);      
    });

    // for sorting i guess concurrentBag has orderby; if not working convert to list
    return new FindStudies_DTO_OUT(ret.Sort(findStudies_DTO_IN.SortColumnName, findStudies_DTO_IN.SortOrderBy));
}

注意:代码尚未测试,但要点在那里。我也觉得 task.run 里面的 task.run 是个坏主意,不记得我读过哪篇文章(可能来自 Stephen Cleary,不确定)。

【讨论】:

  • 感谢您的回答,但我认为这不是正确的解决方案,因为如果端点不可用,Ping 需要很长时间才能获得等于 receivetimeout 的响应。
  • 如果我没记错,如果端点不可用,它会立即抛出异常。
  • 不,你错了,已经测试过这个原因我必须将一个任务运行到另一个任务中。
猜你喜欢
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多