4

我第一次尝试使用依赖注入来松散耦合一个新的应用程序。我的问题是如何将状态信息传回给用户。在过去,所有代码都塞进 GUI 中,如果非常混乱且无法维护,这很容易。类的安排是这样的(请不要检查我的 UML 技能——它们不存在):

应用概述

如果我们选择右手边。AirportsInformationRepository 只存储数据并在被询问时提供给控制器。一开始,它使用 Persist 类获取信息,以从用户硬盘驱动器中获取与给定过滤器匹配的文件。它使用反编译器从文件中提取信息。所有这些都可以正常工作,并且信息本身会按应有的方式进入 GUI。

同时,我的问题是如何告诉用户正在发生的事情。这可能发生在反编译器中,例如,如果它获得的文件无法反编译或可能不包含数据。如果配置文件在说谎并且某些文件夹不存在,则它可能发生在 Persist 类中。除非出现致命错误,否则此类问题不应停止该过程。

如果出现致命错误,则需要立即返回给用户,整个过程应该停止。否则,可以通过该过程以某种方式收集警告,然后在扫描完成时显示。

我熟悉日志记录,并且该应用程序确实有一个记录器来监视应用程序是否存在未处理的异常和其他故障。这写入磁盘,我像大多数人一样使用它来处理错误。我不想将其用于向用户报告状态,因为老实说,如果找不到文件或用户输入了配置文件的无效路径,则应用程序没有任何问题。

我考虑过:

  • 在每个类中累积一个日志,并在进程完成(或失败)时将其传递回消费者。我发现这真的很乱
  • 使用事件,但如果消费者正在订阅并且事件以与日志相同的方式传递链,那么我认为这并没有好得多。我想另一种方法是让 GUI 直接订阅,但它不应该对反编译器一无所知....
  • 一个家庭滚动记录器,它可以是静态类,也可以在 program.cs 中实例化
  • 某种消息传递框架——我承认我不清楚,但我认为它与中央事件处理程序非常相似,GUI 可以订阅它而无需了解其他类的任何信息。

所以总结一下。什么是积累“一切照旧”状态信息并在扫描结束时将其提供给 GUI 的最佳方式,同时能够在致命问题上停止。

提前感谢您阅读本文。为帖子的长度道歉。

编辑 我应该说该应用程序使用的是 NET 3.5。我会改变它以获得一个优雅的解决方案,但是......

4

2 回答 2

3

当您处理典型的“流”处理时,甚至可能是多线程/并发处理流,最好的方法是“邮槽”/消息泵。通过交换消息,您可以轻松地协调应用程序的多个层,包括报告错误、通知下一个命令链等。我不是指 Windows 消息,我的意思是:

public abstract class Message
{
   public abstract void Process();
} 

然后:

public class MessageQueue
{
   private Queue m_Queue;
   public void Post(Message msg) {....}
   public void Process() {.....}
}

然后在应用程序的每个线程/处理层上分配一个 MessageQueue 并传递消息,如下所示:

    GUIMessageQueue.Post(
           new ErrorMessage("Internal async file reader ran out of buffer"));  

在 GUI 线程上放置一个读取 GUI 队列并将其称为 Process() 的计时器。现在,您可以创建许多消息派生的工作项来执行各种任务,这些任务非常容易在线程/逻辑层之间进行编排。此外,消息可能包含与它们相关的数据块的引用:

public AirplaneLandedMessage: Message { public Airplane Plane ...... }

这是我在大规模并行链处理系统中使用的一些真实代码:

/// <summary>
/// Defines a base for items executable by WorkQueue
/// </summary>
public interface IWorkItem<TContext> where TContext : class
{
  /// <summary>
  /// Invoked on an item to perform actual work. 
  /// For example: repaint grid from changed data source, refresh file, send email etc... 
  /// </summary>
  void PerformWork(TContext context);

  /// <summary>
  /// Invoked after successfull work execution - when no exception happened
  /// </summary>
  void WorkSucceeded();

  /// <summary>
  /// Invoked when either work execution or work success method threw an exception and did not succeed
  /// </summary>
  /// <param name="workPerformed">When true indicates that PerformWork() worked without exception but exception happened later</param>
  void WorkFailed(bool workPerformed, Exception error);

}


/// <summary>
/// Defines contract for work queue that work items can be posted to
/// </summary>
public interface IWorkQueue<TContext> where TContext : class
{
  /// <summary>
  /// Posts work item into the queue in natural queue order (at the end of the queue)
  /// </summary>
  void PostItem(IWorkItem<TContext> work);

  long ProcessedSuccessCount{get;}
  long ProcessedFailureCount{get;}

  TContext Context { get; }
}
于 2012-12-27T17:29:22.073 回答
1

好吧,致命错误是容易的部分,它们只是通过异常处理。工作线程在遇到阻止继续工作的问题时可以抛出异常,然后在某个时候当它冒泡到 UI 层时,您可以捕获异常并向用户显示适当的错误消息,而无需实际操作使应用程序崩溃。您可以使用不同类型的异常和异常消息来允许不同的问题导致不同的 UI 响应。

至于指示非致命状态,您可以使用该IProgress接口。在您的 UI 层中,您可以创建一个Progress实例,该实例在被调用时会更新......无论新状态如何。然后,您可以IProgress通过工作人员类传递实例,并在您有信息要提供时触发它。

由于您是 4.5 之前的版本,因此重写此类很容易。

public interface IProgress<T>
{
    public void Report(T parameter);
}

public class Progress<T>:IProgress<T>
{
    public event Action<T> ProgressChanged;

    public Progress() { }
    public Progress(Action<T> action)
    {
        ProgressChanged += action;
    }

    void IProgress<T>.Report(T parameter)
    {
        ProgressChanged(parameter);
    }
}

注意:真正的Progress类将事件编组到 UI 线程中,我没有添加那部分,所以要么添加它,要么在事件处理程序中执行它。

于 2012-12-27T17:12:41.247 回答