Намерение: в моем приложении C#/WPF у меня есть Logger (реализованный как Singleton), который широко используется во всем приложении для регистрации различных поведение:
Код: Выделить всё
public class Logger
{
public ReadOnlyCollection Log => _logRo;
private readonly ObservableCollection _log;
private readonly ReadOnlyObservableCollection _logRo;
private static Logger? _instance;
private Dictionary _logActors;
public static Logger GetInstance()
{
return _instance ??= new Logger();
}
public Logger()
{
_log = [];
_logRo = new(_log);
_logActors = new Dictionary
{
{ LogLevels.Trace, [new ConsoleWriter(), new FileWriter()] },
{ LogLevels.Debug, [new ConsoleWriter(), new FileWriter()] },
{ LogLevels.Info, [new ConsoleWriter(), new FileWriter()] },
{ LogLevels.Warning, [new ConsoleWriter(), new FileWriter()] },
{ LogLevels.Error, [new ConsoleWriter(), new FileWriter()] },
{ LogLevels.Fatal, [new ConsoleWriter(), new FileWriter()] },
};
}
public static void Add(LogLevels level, string message)
{
var instance = GetInstance();
LogEntry entry = new(level, message);
instance?._log.Add(entry);
List? actors = instance?._logActors[level];
if (actors == null) return;
foreach (var actor in actors)
{
actor.Perform(entry);
}
}
public void Clear()
{
_log.Clear();
}
private interface ILogActor
{
void Perform(LogEntry entry);
}
private class ConsoleWriter : ILogActor
{
public void Perform(LogEntry entry)
{
Console.WriteLine(entry.ToString());
}
}
private class FileWriter : ILogActor
{
public void Perform(LogEntry entry)
{
var folder = Utilities.FileOperations.GetAppPath();
const string name = "debug.log";
var path = folder + name;
Utilities.FileOperations.AppendToFile(path, entry.ToString() + Environment.NewLine);
}
}
public class LogEntry(LogLevels level, string message)
{
public DateTime Time { get; init; } = DateTime.Now;
public LogLevels Level { get; init; } = level;
public string Message { get; init; } = message;
public override string ToString()
{
List entryItems = [Time.ToString(CultureInfo.InvariantCulture), Utilities.EnumTools.GetEnumDescription(Level), Message];
return entryItems.Aggregate(string.Empty, (current, item) => current + (item + "\t"));
}
}
}
[Flags]
public enum LogLevels
{
[Description("TRACE")]
Trace = 1,
[Description("DEBUG")]
Debug = 2,
[Description("INFO")]
Info = 4,
[Description("WARNING")]
Warning = 8,
[Description("ERROR")]
Error = 16,
[Description("FATAL")]
Fatal = 32
}
Страница:
Код: Выделить всё
public partial class LoggerPage : Page
{
LoggerPageViewModel vm;
public LoggerPage(AppSettingsData.GeneralSettings generalSettings)
{
InitializeComponent();
vm = new LoggerPageViewModel(generalSettings);
DataContext = vm;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var view = (CollectionView)CollectionViewSource.GetDefaultView(Log_ListView.ItemsSource);
view.Filter = UserFilter;
}
private bool UserFilter(object item)
{
var level = (item as Logger.LogEntry).Level;
if (vm.LogDisplayLevels.HasFlag(level))
{
return true;
}
return false;
}
private void CheckboxChanged(object sender, RoutedEventArgs e)
{
CollectionViewSource.GetDefaultView(Log_ListView.ItemsSource).Refresh();
}
public class LoggerPageViewModel
{
public ReadOnlyCollection Log { get; set; }
public LogLevels LogDisplayLevels { get; init; }
public LoggerPageViewModel(AppSettingsData.GeneralSettings generalSettings)
{
Log = Logger.GetInstance().Log;
LogDisplayLevels = generalSettings.DisplayLogLevels;
}
}
}
public class LogMaskValueConverter : IValueConverter
{
private LogLevels target;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var mask = (LogLevels)parameter;
target = (LogLevels)value;
return (mask & target) != 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
target ^= (LogLevels)parameter;
return target;
}
}
< /code>
Код: Выделить всё
public partial class LoggerWindow : Window
{
public LoggerWindow(AppSettingsData.GeneralSettings generalSettings)
{
InitializeComponent();
LogDisplay_Frame.Navigate(new LoggerPage(generalSettings));
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
Logger.GetInstance().Clear();
}
}
< /code>
< /code>
Теперь я хочу отобразить это окно, когда начинается приложение, и для того, чтобы обновить его содержимое в прямом эфире, когда приложение запускается без замораживания, обновление, как только новые записи добавляются в Logger .Log Код: Выделить всё
public partial class App : System.Windows.Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var loggerWindow = new LoggerWindow(GeneralSettings);
loggerWindow.Show();
RunTestsAsync();
}
private async void RunTestsAsync()
{
await Task.Run(() =>
{
Tests.Run(); //Various calls that take ~1 minute to complete, with many calls to Logger.Add
});
}
}
Код: Выделить всё
public static class GeneralSettings
{
public LogLevels DisplayLogLevels { get; init; } = LogLevels.Info | LogLevels.Error | LogLevels.Fatal;
}
Этот тип CollectionView не поддерживает изменения в своей SourceCollection из потока, отличного от потока Dispatcher
Насколько я понимаю, проблема в том, что Logger.Log (ObservableCollection) обрабатывается двумя потоками: один из них изменяет его, а другой подписывается на события изменения его содержимого, что не разрешено. Однако это и есть мое фактическое намерение — я хочу, чтобы ядро приложения продолжало писать в этот Logger.Log, а LoggerPage немедленно замечало эти изменения и обновляло пользовательский интерфейс. Я не знаю, как это решить.
Что я здесь делаю не так? Мой подход неверен? Если да, то как мне лучше с этим справиться?
Я вижу, что в подобных вопросах советовали использовать EnableCollectionSynchronization, но в документации говорится, что для работы его необходимо вызывать из потока пользовательского интерфейса (я попробовал это из фонового потока, который использует Logger, и это не сработало), поэтому я не уверен, что это можно применить к моему коду без добавления инвазивных общедоступных вызовов в Logger, чтобы предоставить этот вызов потоку пользовательского интерфейса. . Я прошу повторно открыть этот вопрос, если я не ошибаюсь и эта ситуация требует другого решения, но если я упускаю что-то очевидное, прокомментируйте.
Подробнее здесь: https://stackoverflow.com/questions/793 ... her-thread
Мобильная версия