前六章已经让你具备了独立开发Revit插件的能力。但实际工程中,我们往往需要应对更复杂的场景:自动响应模型变化、与外部系统通信、处理大量数据时的性能瓶颈 。本章作为进阶篇,将带你探索更高级的API应用。
7.1 Revit事件监听(Event Handling)
事件监听让你能够在特定操作发生时自动执行自定义代码,是实现自动化工作流的核心技术。
7.1.1 事件类型概览
| 事件类别 | 常用事件 | 触发时机 | 典型应用 |
|---|---|---|---|
| 文档事件 | DocumentOpened |
打开文档时 | 自动加载配置、初始化环境 |
DocumentSaving |
保存文档前 | 自动备份、数据校验 | |
DocumentSaved |
保存文档后 | 同步导出报告 | |
DocumentClosing |
关闭文档前 | 清理临时数据 | |
| 应用事件 | ApplicationInitialized |
Revit启动完成 | 注册全局热键 |
ApplicationClosing |
Revit关闭前 | 保存日志 | |
| 视图事件 | ViewActivated |
切换视图时 | 自动应用视图模板 |
| 选择事件 | SelectionChanged |
选择集变化时 | 实时显示构件属性 |
| 命令事件 | CommandExecuted |
任意命令执行后 | 操作日志记录 |
7.1.2 实战:自动同步构件编号
场景:当用户修改了某个构件的"标记"参数后,自动更新其关联构件的编号。
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Events;
namespace AdvancedTools
{
[Transaction(TransactionMode.Manual)]
public class EventMonitorApp : IExternalApplication
{
private Document _currentDoc;
private Dictionary<ElementId, string> _elementMarkCache = new Dictionary<ElementId, string>();
public Result OnStartup(UIControlledApplication application)
{
// 注册文档打开事件
application.ControlledApplication.DocumentOpened += OnDocumentOpened;
// 注册文档保存后事件(用于验证)
application.ControlledApplication.DocumentSaved += OnDocumentSaved;
// 注册选择变化事件(需要UIApplication)
// 注意:此事件需要在有UIDocument时才能使用,推荐在命令中动态注册
return Result.Succeeded;
}
public Result OnShutdown(UIControlledApplication application)
{
// 取消事件注册(防止内存泄漏)
application.ControlledApplication.DocumentOpened -= OnDocumentOpened;
application.ControlledApplication.DocumentSaved -= OnDocumentSaved;
return Result.Succeeded;
}
/// <summary>
/// 文档打开时初始化缓存
/// </summary>
private void OnDocumentOpened(object sender, DocumentOpenedEventArgs args)
{
_currentDoc = args.Document;
// 加载所有构件的"标记"到缓存
LoadMarkCache(_currentDoc);
// 注册文档修改事件(当参数变化时触发)
_currentDoc.DocumentChanged += OnDocumentChanged;
}
/// <summary>
/// 文档修改时检测"标记"参数变化
/// </summary>
private void OnDocumentChanged(object sender, DocumentChangedEventArgs args)
{
Document doc = sender as Document;
if (doc == null) return;
// 获取所有被修改的元素
ICollection<ElementId> modifiedIds = args.GetModifiedElementIds();
if (modifiedIds == null || modifiedIds.Count == 0) return;
List<ElementId> markChangedElements = new List<ElementId>();
foreach (ElementId id in modifiedIds)
{
Element elem = doc.GetElement(id);
if (elem == null) continue;
// 检查"标记"参数是否变化
string currentMark = ParameterHelper.GetParameterString(elem, "标记");
if (_elementMarkCache.TryGetValue(id, out string oldMark))
{
if (currentMark != oldMark)
{
// 标记发生了变化!
markChangedElements.Add(id);
Console.WriteLine($"构件 {elem.Id} 的标记从 '{oldMark}' 变为 '{currentMark}'");
}
}
}
// 如果有标记变化,执行自动联动逻辑
if (markChangedElements.Count > 0)
{
HandleMarkChange(doc, markChangedElements);
}
// 更新缓存
RefreshMarkCache(doc, modifiedIds);
}
/// <summary>
/// 处理标记变化:更新关联构件
/// </summary>
private void HandleMarkChange(Document doc, List<ElementId> changedIds)
{
using (Transaction trans = new Transaction(doc, "自动更新关联编号"))
{
trans.Start();
foreach (ElementId id in changedIds)
{
Element elem = doc.GetElement(id);
string newMark = ParameterHelper.GetParameterString(elem, "标记");
if (string.IsNullOrEmpty(newMark)) continue;
// 示例:如果修改了墙的标记,自动更新同一楼层的所有门
if (elem is Wall wall)
{
string levelName = GetElementLevelName(doc, wall);
// 查找同一楼层、同一区域的门
FilteredElementCollector collector = new FilteredElementCollector(doc);
var doors = collector
.OfCategory(BuiltInCategory.OST_Doors)
.Cast<FamilyInstance>()
.Where(d => GetElementLevelName(doc, d) == levelName)
.ToList();
foreach (FamilyInstance door in doors)
{
string doorMark = ParameterHelper.GetParameterString(door, "标记");
if (string.IsNullOrEmpty(doorMark))
{
// 自动生成门编号:W-墙标记-D-序号
string newDoorMark = $"{newMark}-D-{GetNextDoorNumber(doc, levelName)}";
ParameterHelper.SetParameterValue(door, "标记", newDoorMark);
}
}
}
}
trans.Commit();
}
}
private string GetElementLevelName(Document doc, Element elem)
{
Parameter levelParam = elem.get_Parameter(BuiltInParameter.LEVEL_PARAM);
if (levelParam != null && levelParam.HasValue)
{
ElementId levelId = levelParam.AsElementId();
if (levelId != null)
{
Element level = doc.GetElement(levelId);
return level?.Name ?? "未知";
}
}
return "未知";
}
private int GetNextDoorNumber(Document doc, string levelName)
{
// 统计该楼层已有门数量
FilteredElementCollector collector = new FilteredElementCollector(doc);
var doors = collector
.OfCategory(BuiltInCategory.OST_Doors)
.Cast<FamilyInstance>()
.Where(d => GetElementLevelName(doc, d) == levelName)
.Count();
return doors + 1;
}
private void LoadMarkCache(Document doc)
{
_elementMarkCache.Clear();
FilteredElementCollector collector = new FilteredElementCollector(doc);
var elements = collector
.OfClass(typeof(FamilyInstance))
.Cast<FamilyInstance>()
.Where(e => e.Category != null)
.ToList();
foreach (Element elem in elements)
{
string mark = ParameterHelper.GetParameterString(elem, "标记");
if (!string.IsNullOrEmpty(mark))
{
_elementMarkCache[elem.Id] = mark;
}
}
}
private void RefreshMarkCache(Document doc, ICollection<ElementId> ids)
{
foreach (ElementId id in ids)
{
Element elem = doc.GetElement(id);
if (elem == null) continue;
string mark = ParameterHelper.GetParameterString(elem, "标记");
if (!string.IsNullOrEmpty(mark))
{
_elementMarkCache[id] = mark;
}
else
{
_elementMarkCache.Remove(id);
}
}
}
private void OnDocumentSaved(object sender, DocumentSavedEventArgs args)
{
// 保存时记录日志
string logMessage = $"文档 {args.Document.Title} 于 {DateTime.Now} 被保存";
Console.WriteLine(logMessage);
// 可在此写入日志文件
WriteLog(logMessage);
}
private void WriteLog(string message)
{
string logPath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"RevitLogs",
$"log_{DateTime.Now:yyyyMMdd}.txt"
);
System.IO.Directory.CreateDirectory(
System.IO.Path.GetDirectoryName(logPath)
);
System.IO.File.AppendAllText(logPath,
$"{DateTime.Now:HH:mm:ss} - {message}{Environment.NewLine}"
);
}
}
}
7.1.3 事件监听的最佳实践
| 最佳实践 | 说明 |
|---|---|
| 避免耗时操作 | 事件处理中不要执行长时间任务,可改用异步或后台线程 |
| 注意事务管理 | 在事件中修改文档必须使用新的事务,不要使用正在执行的事务 |
| 及时取消注册 | 在 OnShutdown 中取消所有事件注册,防止内存泄漏 |
| 异常捕获 | 事件中一定要加 try-catch,否则异常可能导致Revit崩溃 |
| 避免无限循环 | 事件中修改文档可能再次触发相同事件,需设计防重入机制 |
7.2 外部服务集成(Web API调用)
现代BIM应用往往需要与外部系统(如项目管理平台、物料系统)进行数据交互。
7.2.1 场景:同步构件数据到云端
csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace AdvancedTools
{
public class CloudSyncService
{
private static readonly HttpClient _httpClient = new HttpClient();
private const string API_BASE_URL = "https://your-api-server.com/api/v1";
static CloudSyncService()
{
_httpClient.Timeout = TimeSpan.FromSeconds(30);
_httpClient.DefaultRequestHeaders.Add("User-Agent", "RevitPlugin/1.0");
}
/// <summary>
/// 同步构件数据到云端
/// </summary>
public async Task<SyncResult> SyncElementsAsync(
Document doc,
List<ElementId> elementIds,
string apiToken)
{
SyncResult result = new SyncResult { SuccessCount = 0, FailedCount = 0 };
if (string.IsNullOrEmpty(apiToken))
{
result.ErrorMessage = "未提供API令牌";
return result;
}
// 设置认证头
if (_httpClient.DefaultRequestHeaders.Contains("Authorization"))
{
_httpClient.DefaultRequestHeaders.Remove("Authorization");
}
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiToken}");
foreach (ElementId id in elementIds)
{
Element elem = doc.GetElement(id);
if (elem == null) continue;
try
{
// 构建构件数据
ElementSyncData data = BuildSyncData(elem);
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
// 发送POST请求
HttpResponseMessage response = await _httpClient.PostAsync(
$"{API_BASE_URL}/elements/sync",
content
);
if (response.IsSuccessStatusCode)
{
result.SuccessCount++;
}
else
{
result.FailedCount++;
result.ErrorMessage = $"同步失败: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}";
}
}
catch (Exception ex)
{
result.FailedCount++;
result.ErrorMessage = $"异常: {ex.Message}";
}
}
return result;
}
private ElementSyncData BuildSyncData(Element elem)
{
ElementSyncData data = new ElementSyncData
{
ElementId = elem.Id.IntegerValue,
UniqueId = elem.UniqueId,
Name = elem.Name,
Category = elem.Category?.Name,
Level = GetLevelName(elem),
Mark = ParameterHelper.GetParameterString(elem, "标记"),
TypeName = GetTypeName(elem),
SyncTime = DateTime.Now
};
// 获取位置信息
Location loc = elem.Location;
if (loc is LocationPoint pointLoc)
{
data.LocationX = pointLoc.Point.X;
data.LocationY = pointLoc.Point.Y;
data.LocationZ = pointLoc.Point.Z;
}
else if (loc is LocationCurve curveLoc)
{
XYZ start = curveLoc.Curve.GetEndPoint(0);
XYZ end = curveLoc.Curve.GetEndPoint(1);
data.LocationX = (start.X + end.X) / 2;
data.LocationY = (start.Y + end.Y) / 2;
data.LocationZ = (start.Z + end.Z) / 2;
}
// 获取几何参数
if (elem is FamilyInstance instance)
{
data.Width = GetParameterDouble(instance, BuiltInParameter.FAMILY_WIDTH_PARAM);
data.Height = GetParameterDouble(instance, BuiltInParameter.FAMILY_HEIGHT_PARAM);
}
else if (elem is Wall wall)
{
data.Width = wall.Width;
data.Height = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM)?.AsDouble() ?? 0;
}
return data;
}
private string GetLevelName(Element elem)
{
Parameter levelParam = elem.get_Parameter(BuiltInParameter.LEVEL_PARAM);
if (levelParam != null && levelParam.HasValue)
{
Element level = elem.Document.GetElement(levelParam.AsElementId());
return level?.Name ?? "未知";
}
return "未知";
}
private string GetTypeName(Element elem)
{
if (elem is FamilyInstance instance)
{
return instance.Symbol?.Name ?? "未知";
}
else if (elem is Wall wall)
{
return wall.WallType?.Name ?? "未知";
}
return elem.GetType().Name;
}
private double GetParameterDouble(Element elem, BuiltInParameter param)
{
Parameter p = elem.get_Parameter(param);
return p?.AsDouble() ?? 0;
}
public class SyncResult
{
public int SuccessCount { get; set; }
public int FailedCount { get; set; }
public string ErrorMessage { get; set; }
}
private class ElementSyncData
{
public int ElementId { get; set; }
public string UniqueId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public string Level { get; set; }
public string Mark { get; set; }
public string TypeName { get; set; }
public double LocationX { get; set; }
public double LocationY { get; set; }
public double LocationZ { get; set; }
public double Width { get; set; }
public double Height { get; set; }
public DateTime SyncTime { get; set; }
}
}
}
7.2.2 带进度显示的同步命令
csharp
[Transaction(TransactionMode.ReadOnly)]
public class SyncToCloudCommand : IExternalCommand
{
public async Task<Result> ExecuteAsync(ExternalCommandData commandData,
ref string message, ElementSet elements)
{
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Document doc = uidoc.Document;
// 获取选中的元素
ICollection<ElementId> selectedIds = uidoc.Selection.GetElementIds();
if (selectedIds.Count == 0)
{
TaskDialog.Show("提示", "请先选中要同步的构件!");
return Result.Cancelled;
}
// 获取API令牌(实际使用中应从安全存储获取)
string apiToken = GetApiToken();
if (string.IsNullOrEmpty(apiToken))
{
TaskDialog.Show("错误", "未配置API令牌,请先登录!");
return Result.Failed;
}
// 显示进度对话框
ProgressDialog progress = new ProgressDialog("正在同步到云端...");
IntPtr revitHandle = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
new WindowInteropHelper(progress).Owner = revitHandle;
progress.Show();
// 执行同步
CloudSyncService service = new CloudSyncService();
var result = await service.SyncElementsAsync(doc, selectedIds.ToList(), apiToken);
progress.Close();
// 显示结果
TaskDialog.Show("同步完成",
$"成功: {result.SuccessCount}\n" +
$"失败: {result.FailedCount}\n" +
(string.IsNullOrEmpty(result.ErrorMessage) ? "" : $"错误: {result.ErrorMessage}"));
return Result.Succeeded;
}
private string GetApiToken()
{
// 从配置文件或注册表读取
// 实际使用中建议使用加密存储
return System.Configuration.ConfigurationManager.AppSettings["ApiToken"];
}
}
7.3 性能优化策略
当处理大量数据(数千个构件)时,代码性能会显著影响用户体验。
7.3.1 性能瓶颈分析
| 操作 | 耗时原因 | 影响程度 |
|---|---|---|
| 遍历所有参数 | 每个参数都需反射调用 | ⭐⭐⭐⭐⭐ |
频繁调用 doc.GetElement(id) |
I/O操作 | ⭐⭐⭐⭐ |
| 未批量化的事务提交 | 每次修改都提交事务 | ⭐⭐⭐⭐⭐ |
| LINQ在大集合上的复杂查询 | 算法复杂度高 | ⭐⭐⭐ |
7.3.2 优化技巧
技巧1:使用收集器的批量加载
csharp
// ❌ 不推荐:循环中逐个获取
foreach (ElementId id in ids)
{
Element elem = doc.GetElement(id); // 每次都访问文档
// 处理...
}
// ✅ 推荐:一次性批量加载
ICollection<Element> elements = new FilteredElementCollector(doc)
.WherePasses(new ElementIdSetFilter(ids.ToHashSet()))
.ToElements();
技巧2:减少参数访问次数
csharp
// ❌ 不推荐:每次读取单独调用
foreach (Element elem in elements)
{
string param1 = elem.LookupParameter("参数1")?.AsString();
string param2 = elem.LookupParameter("参数2")?.AsString();
}
// ✅ 推荐:缓存参数引用
foreach (Element elem in elements)
{
Parameter p1 = elem.LookupParameter("参数1");
Parameter p2 = elem.LookupParameter("参数2");
// 一次性读取
}
技巧3:合并事务
csharp
// ❌ 不推荐:每个元素单独事务
foreach (Element elem in elements)
{
using (Transaction trans = new Transaction(doc, "修改"))
{
trans.Start();
// 修改一个元素
trans.Commit();
}
}
// ✅ 推荐:所有修改用一个事务
using (Transaction trans = new Transaction(doc, "批量修改"))
{
trans.Start();
foreach (Element elem in elements)
{
// 修改所有元素
}
trans.Commit();
}
技巧4:使用并行处理(对于只读操作)
csharp
// 并行读取参数(仅用于只读操作)
Parallel.ForEach(elements, elem =>
{
string mark = ParameterHelper.GetParameterString(elem, "标记");
// 将结果存入线程安全集合
ConcurrentBag<ElementInfo> bag = new ConcurrentBag<ElementInfo>();
bag.Add(new ElementInfo { Id = elem.Id, Mark = mark });
});
7.3.3 性能优化的完整示例
csharp
/// <summary>
/// 高性能的批量参数读取器
/// </summary>
public class HighPerformanceParameterReader
{
private Document _doc;
private Dictionary<ElementId, Dictionary<string, string>> _cache;
public HighPerformanceParameterReader(Document doc)
{
_doc = doc;
_cache = new Dictionary<ElementId, Dictionary<string, string>>();
}
/// <summary>
/// 批量读取多个元素的多个参数(性能优化版)
/// </summary>
public Dictionary<ElementId, Dictionary<string, string>> ReadParameters(
List<ElementId> elementIds,
List<string> paramNames)
{
// 1. 批量获取元素(减少I/O)
var elements = new FilteredElementCollector(_doc)
.WherePasses(new ElementIdSetFilter(elementIds.ToHashSet()))
.Cast<Element>()
.ToDictionary(e => e.Id);
// 2. 并行读取参数(针对只读操作)
var result = new ConcurrentDictionary<ElementId, Dictionary<string, string>>();
Parallel.ForEach(elements.Values, elem =>
{
var paramValues = new Dictionary<string, string>();
// 批量查找参数
foreach (string paramName in paramNames)
{
Parameter param = elem.LookupParameter(paramName);
if (param != null && param.HasValue)
{
paramValues[paramName] = param.AsString() ?? "";
}
}
result[elem.Id] = paramValues;
});
return result.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
/// <summary>
/// 批量修改参数(优化的单事务方式)
/// </summary>
public void BatchSetParameters(
Dictionary<ElementId, Dictionary<string, string>> updates,
Action<int, int> progressCallback = null)
{
int total = updates.Count;
int processed = 0;
using (Transaction trans = new Transaction(_doc, "批量修改参数"))
{
trans.Start();
foreach (var kvp in updates)
{
ElementId id = kvp.Key;
Element elem = _doc.GetElement(id);
if (elem == null) continue;
foreach (var paramUpdate in kvp.Value)
{
Parameter param = elem.LookupParameter(paramUpdate.Key);
if (param != null && !param.IsReadOnly)
{
param.Set(paramUpdate.Value);
}
}
processed++;
progressCallback?.Invoke(processed, total);
}
trans.Commit();
}
}
}
7.4 异步编程与Revit
Revit API本身是单线程的,但你可以结合异步操作改善用户体验。
7.4.1 异步命令实现
csharp
[Transaction(TransactionMode.ReadOnly)]
public class AsyncCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData,
ref string message, ElementSet elements)
{
// 使用async/await模式
var task = ExecuteAsync(commandData, ref message, elements);
task.Wait(); // 等待异步完成
return task.Result;
}
private async Task<Result> ExecuteAsync(ExternalCommandData commandData,
ref string message, ElementSet elements)
{
// 模拟长时间操作
await Task.Delay(5000);
// 在后台线程处理数据(只读操作)
var result = await Task.Run(() =>
{
// 这里执行只读操作
return ProcessData(commandData);
});
// 回到主线程更新UI
// 注意:Revit操作必须回到主线程
return result;
}
private Result ProcessData(ExternalCommandData commandData)
{
// 只读数据处理
return Result.Succeeded;
}
}
7.4.2 使用BackgroundWorker显示进度
csharp
using System.ComponentModel;
using System.Threading;
public class ProgressCommand : IExternalCommand
{
public Result Execute(ExternalCommandData commandData,
ref string message, ElementSet elements)
{
var backgroundWorker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = false
};
backgroundWorker.DoWork += (sender, e) =>
{
var worker = sender as BackgroundWorker;
var data = e.Argument as ProcessData;
// 执行耗时操作
for (int i = 0; i < data.Total; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
// 处理单个元素
ProcessElement(data.Elements[i]);
// 报告进度
int percent = (i + 1) * 100 / data.Total;
worker.ReportProgress(percent, $"处理第 {i + 1}/{data.Total} 个");
}
e.Result = data;
};
backgroundWorker.ProgressChanged += (sender, e) =>
{
// 更新UI进度
string status = e.UserState as string;
UpdateProgress(e.ProgressPercentage, status);
};
backgroundWorker.RunWorkerCompleted += (sender, e) =>
{
if (e.Error != null)
{
ShowError(e.Error.Message);
}
else if (e.Cancelled)
{
ShowMessage("操作已取消");
}
else
{
ShowMessage("操作完成!");
}
};
// 启动后台任务
var data = new ProcessData { Elements = GetElements(), Total = 100 };
backgroundWorker.RunWorkerAsync(data);
return Result.Succeeded;
}
}
7.5 错误处理与日志系统
一个健壮的插件必须有完善的错误处理和日志系统。
7.5.1 自定义异常类
csharp
namespace AdvancedTools.Exceptions
{
/// <summary>
/// 插件基础异常
/// </summary>
public class PluginException : Exception
{
public string ErrorCode { get; set; }
public PluginException() : base() { }
public PluginException(string message) : base(message) { }
public PluginException(string message, Exception inner) : base(message, inner) { }
public PluginException(string errorCode, string message) : base(message)
{
ErrorCode = errorCode;
}
}
/// <summary>
/// 参数相关异常
/// </summary>
public class ParameterException : PluginException
{
public string ParameterName { get; set; }
public ElementId ElementId { get; set; }
public ParameterException(string paramName, ElementId elementId, string message)
: base("PARAM_001", message)
{
ParameterName = paramName;
ElementId = elementId;
}
}
/// <summary>
/// 网络/API相关异常
/// </summary>
public class ApiException : PluginException
{
public int StatusCode { get; set; }
public ApiException(int statusCode, string message)
: base($"API_{statusCode}", message)
{
StatusCode = statusCode;
}
}
}
7.5.2 日志管理器
csharp
using System;
using System.IO;
using System.Threading;
namespace AdvancedTools.Logging
{
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private string _logDirectory;
private string _currentLogFile;
public enum LogLevel
{
Debug,
Info,
Warning,
Error,
Fatal
}
private Logger()
{
_logDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyRevitPlugin",
"Logs"
);
Directory.CreateDirectory(_logDirectory);
_currentLogFile = Path.Combine(_logDirectory,
$"log_{DateTime.Now:yyyyMMdd}.txt");
}
public static Logger Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Logger();
}
}
}
return _instance;
}
}
public void Debug(string message)
{
Log(LogLevel.Debug, message);
}
public void Info(string message)
{
Log(LogLevel.Info, message);
}
public void Warning(string message)
{
Log(LogLevel.Warning, message);
}
public void Error(string message, Exception ex = null)
{
string fullMessage = ex != null
? $"{message}{Environment.NewLine}异常: {ex.Message}{Environment.NewLine}堆栈: {ex.StackTrace}"
: message;
Log(LogLevel.Error, fullMessage);
}
public void Fatal(string message, Exception ex = null)
{
string fullMessage = ex != null
? $"{message}{Environment.NewLine}异常: {ex.Message}{Environment.NewLine}堆栈: {ex.StackTrace}"
: message;
Log(LogLevel.Fatal, fullMessage);
}
private void Log(LogLevel level, string message)
{
string logEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{level.ToString().ToUpper()}] {message}";
try
{
// 写入文件(异步写入提高性能)
ThreadPool.QueueUserWorkItem(_ =>
{
lock (_lock)
{
File.AppendAllText(_currentLogFile, logEntry + Environment.NewLine);
}
});
}
catch
{
// 日志写入失败不应影响主程序
}
}
/// <summary>
/// 清理7天前的日志文件
/// </summary>
public void CleanOldLogs()
{
try
{
var files = Directory.GetFiles(_logDirectory, "log_*.txt");
foreach (string file in files)
{
var fileInfo = new FileInfo(file);
if (fileInfo.CreationTime < DateTime.Now.AddDays(-7))
{
File.Delete(file);
}
}
}
catch { }
}
}
/// <summary>
/// 全局异常处理器
/// </summary>
public class GlobalExceptionHandler
{
public static void Setup()
{
// 捕获未处理的异常
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
Exception ex = args.ExceptionObject as Exception;
Logger.Instance.Fatal("程序发生未处理异常", ex);
};
// 捕获线程异常
System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (sender, args) =>
{
Logger.Instance.Error("任务异常", args.Exception);
args.SetObserved();
};
}
}
}
7.6 本章小结
| 主题 | 核心内容 | 应用价值 |
|---|---|---|
| 事件监听 | 文档/应用/视图事件 | 自动化工作流、实时同步 |
| Web API集成 | HttpClient + JSON序列化 | BIM与外部系统互联 |
| 性能优化 | 批量操作、缓存、并行 | 处理大规模数据 |
| 异步编程 | async/await, BackgroundWorker | 改善用户体验 |
| 日志系统 | 结构化日志、异常捕获 | 生产环境运维 |