Prism学习日记04 module的使用流程
关于Module的概述
- Module的使用流程
- 几个重要的接口介绍
- IModuleInfo接口
- IModuleCatalog接口
- IModuleManager接口
- IModuleInitializer接口
Module的使用流程
如上图所示,使用模块之前需要:注册模块》发现模块》加载模块》初始化模块
- 注册/发现模块:在运行时为特定应用程序加载模块,对应的目录包含要加载的模块,其位置以及加载顺序的信息。
- 加载模块:模块的程序集被加载到内存中,需要从某个远程位置或本地目录检索模块。
- 初始化模块:创建模块类的实例并通过IModule接口调用它们的Initialize方法。
几个重要的接口介绍
IModuleInfo接口
可以看到这个接口包含了一些关于module的基本信息,比如:
- 依赖项:可以获取被依赖的模块名称
- 初始化模式:可以设置或获取初始化的模式,如下
- 名称:类型:都是对当前module的信息描述
- Ref:可以理解为加载路径
- 状态,当前module的状态信息,比如:
IModuleCatalog接口
之前也简单提到这个接口,可以理解为module的目录,从源码看就是维护了一个IModuleInfo的集合
这里比较重要的就是初始化函数,可以先从ModuleCatalogBase类看到,所谓初始化最终是调用了load函数实现,加载module,可以从三个方式进行加载,这里按照 prism学习日记3中的顺序详细补充。
- 通过通过动态解析xaml文件进行动态加载
注意学习日记3中的方法一通过直接添加module达到静态加载目的,和本次的方法略有不同,此处是直接通过解析xaml文件达到动态加载的目的,无需添加模块引用。xaml示例如下:
只需要在重写的加载模块中读取该xaml即可:
这里贴一下源码
/// <summary>
/// A catalog built from a XAML file.
/// </summary>
public class XamlModuleCatalog : ModuleCatalog
{
private readonly Uri _resourceUri;
private const string _refFilePrefix = "file://";
private int _refFilePrefixLength = _refFilePrefix.Length;
/// <summary>
/// Creates an instance of a XamlResourceCatalog.
/// </summary>
/// <param name="fileName">The name of the XAML file</param>
public XamlModuleCatalog(string fileName)
: this(new Uri(fileName, UriKind.Relative))
{
}
/// <summary>
/// Creates an instance of a XamlResourceCatalog.
/// </summary>
/// <param name="resourceUri">The pack url of the XAML file resource</param>
public XamlModuleCatalog(Uri resourceUri)
{
_resourceUri = resourceUri;
}
/// <summary>
/// Loads the catalog from the XAML file.
/// </summary>
protected override void InnerLoad()
{
var catalog = CreateFromXaml(_resourceUri);
foreach (IModuleCatalogItem item in catalog.Items)
{
if (item is ModuleInfo mi)
{
if (!string.IsNullOrWhiteSpace(mi.Ref))
mi.Ref = GetFileAbsoluteUri(mi.Ref);
}
else if (item is ModuleInfoGroup mg)
{
if (!string.IsNullOrWhiteSpace(mg.Ref))
{
mg.Ref = GetFileAbsoluteUri(mg.Ref);
mg.UpdateModulesRef();
}
else
{
foreach (var module in mg)
{
module.Ref = GetFileAbsoluteUri(module.Ref);
}
}
}
Items.Add(item);
}
}
/// <inheritdoc />
protected override string GetFileAbsoluteUri(string path)
{
//this is to maintain backwards compatibility with the old file:/// and file:// syntax for Xaml module catalog Ref property
if (path.StartsWith(_refFilePrefix + "/", StringComparison.Ordinal))
{
path = path.Substring(_refFilePrefixLength + 1);
}
else if (path.StartsWith(_refFilePrefix, StringComparison.Ordinal))
{
path = path.Substring(_refFilePrefixLength);
}
return base.GetFileAbsoluteUri(path);
}
/// <summary>
/// Creates a <see cref="ModuleCatalog"/> from XAML.
/// </summary>
/// <param name="xamlStream"><see cref="Stream"/> that contains the XAML declaration of the catalog.</param>
/// <returns>An instance of <see cref="ModuleCatalog"/> built from the XAML.</returns>
private static ModuleCatalog CreateFromXaml(Stream xamlStream)
{
if (xamlStream == null)
{
throw new ArgumentNullException(nameof(xamlStream));
}
return XamlReader.Load(xamlStream) as ModuleCatalog;
}
/// <summary>
/// Creates a <see cref="ModuleCatalog"/> from a XAML included as an Application Resource.
/// </summary>
/// <param name="builderResourceUri">Relative <see cref="Uri"/> that identifies the XAML included as an Application Resource.</param>
/// <returns>An instance of <see cref="ModuleCatalog"/> build from the XAML.</returns>
private static ModuleCatalog CreateFromXaml(Uri builderResourceUri)
{
var streamInfo = System.Windows.Application.GetResourceStream(builderResourceUri);
if ((streamInfo != null) && (streamInfo.Stream != null))
{
return CreateFromXaml(streamInfo.Stream);
}
return null;
}
}
- 通过文件夹路径加载
同 prism学习日记3中的描述 - 通过配置文件加载
通过一个ConfigurationStore来逐一解析Configuration下面的modules节点并逐一添加到父类中维护的Modules集合,具体步骤同 prism学习日记3
这里贴一下源码
/// <summary>
/// A catalog built from a configuration file.
/// </summary>
public class ConfigurationModuleCatalog : ModuleCatalog
{
/// <summary>
/// Builds an instance of ConfigurationModuleCatalog with a <see cref="ConfigurationStore"/> as the default store.
/// </summary>
public ConfigurationModuleCatalog()
{
Store = new ConfigurationStore();
}
/// <summary>
/// Gets or sets the store where the configuration is kept.
/// </summary>
public IConfigurationStore Store { get; set; }
/// <summary>
/// Loads the catalog from the configuration.
/// </summary>
protected override void InnerLoad()
{
if (Store == null)
{
throw new InvalidOperationException(Resources.ConfigurationStoreCannotBeNull);
}
EnsureModulesDiscovered();
}
private void EnsureModulesDiscovered()
{
ModulesConfigurationSection section = Store.RetrieveModuleConfigurationSection();
if (section != null)
{
foreach (ModuleConfigurationElement element in section.Modules)
{
IList<string> dependencies = new List<string>();
if (element.Dependencies.Count > 0)
{
foreach (ModuleDependencyConfigurationElement dependency in element.Dependencies)
{
dependencies.Add(dependency.ModuleName);
}
}
ModuleInfo moduleInfo = new ModuleInfo(element.ModuleName, element.ModuleType)
{
Ref = GetFileAbsoluteUri(element.AssemblyFile),
InitializationMode = element.StartupLoaded ? InitializationMode.WhenAvailable : InitializationMode.OnDemand
};
moduleInfo.DependsOn.AddRange(dependencies.ToArray());
AddModule(moduleInfo);
}
}
}
}
IModuleManager接口
/// <summary>
/// Defines the interface for the service that will retrieve and initialize the application's modules.
/// </summary>
public interface IModuleManager
{
/// <summary>
/// Gets all the <see cref="IModuleInfo"/> classes that are in the <see cref="IModuleCatalog"/>.
/// </summary>
IEnumerable<IModuleInfo> Modules { get; }
/// <summary>
/// Initializes the modules marked as <see cref="InitializationMode.WhenAvailable"/> on the <see cref="IModuleCatalog"/>.
/// </summary>
void Run();
/// <summary>
/// Loads and initializes the module on the <see cref="IModuleCatalog"/> with the name <paramref name="moduleName"/>.
/// </summary>
/// <param name="moduleName">Name of the module requested for initialization.</param>
void LoadModule(string moduleName);
/// <summary>
/// Raised repeatedly to provide progress as modules are downloaded.
/// </summary>
event EventHandler<ModuleDownloadProgressChangedEventArgs> ModuleDownloadProgressChanged;
/// <summary>
/// Raised when a module is loaded or fails to load.
/// </summary>
event EventHandler<LoadModuleCompletedEventArgs> LoadModuleCompleted;
}
上述接口定义了
- IEnumerable类型的modules属性:用来描述当前IModuleManager包含的modules
- run方法:从下面的源码看是先对整个应用程序的module解析到ModuleCatalog中,再将这些module中初始化类型是InitializationMode.WhenAvailable 的moudle进行逐一load操作
- LoadModule方法:可以手动加载指定名称的module
- LoadModuleCompleted事件:当module加载结束就会触发此事件
- ModuleDownloadProgressChanged事件:用来描述当前Module如果是从远程下载的,那么通过此事件能够报告当前Module进行下载的Progress
modulemanager的实现类代码如下:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Prism.Properties;
namespace Prism.Modularity
{
/// <summary>
/// Component responsible for coordinating the modules' type loading and module initialization process.
/// </summary>
public partial class ModuleManager : IModuleManager, IDisposable
{
private readonly IModuleInitializer moduleInitializer;
private IEnumerable<IModuleTypeLoader> typeLoaders;
private HashSet<IModuleTypeLoader> subscribedToModuleTypeLoaders = new HashSet<IModuleTypeLoader>();
/// <summary>
/// Initializes an instance of the <see cref="ModuleManager"/> class.
/// </summary>
/// <param name="moduleInitializer">Service used for initialization of modules.</param>
/// <param name="moduleCatalog">Catalog that enumerates the modules to be loaded and initialized.</param>
public ModuleManager(IModuleInitializer moduleInitializer, IModuleCatalog moduleCatalog)
{
this.moduleInitializer = moduleInitializer ?? throw new ArgumentNullException(nameof(moduleInitializer));
ModuleCatalog = moduleCatalog ?? throw new ArgumentNullException(nameof(moduleCatalog));
}
/// <summary>
/// The module catalog specified in the constructor.
/// </summary>
protected IModuleCatalog ModuleCatalog { get; }
/// <summary>
/// Gets all the <see cref="IModuleInfo"/> classes that are in the <see cref="IModuleCatalog"/>.
/// </summary>
public IEnumerable<IModuleInfo> Modules => ModuleCatalog.Modules;
/// <summary>
/// Raised repeatedly to provide progress as modules are loaded in the background.
/// </summary>
public event EventHandler<ModuleDownloadProgressChangedEventArgs> ModuleDownloadProgressChanged;
private void RaiseModuleDownloadProgressChanged(ModuleDownloadProgressChangedEventArgs e)
{
ModuleDownloadProgressChanged?.Invoke(this, e);
}
/// <summary>
/// Raised when a module is loaded or fails to load.
/// </summary>
public event EventHandler<LoadModuleCompletedEventArgs> LoadModuleCompleted;
private void RaiseLoadModuleCompleted(IModuleInfo moduleInfo, Exception error)
{
this.RaiseLoadModuleCompleted(new LoadModuleCompletedEventArgs(moduleInfo, error));
}
private void RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e)
{
this.LoadModuleCompleted?.Invoke(this, e);
}
/// <summary>
/// Initializes the modules marked as <see cref="InitializationMode.WhenAvailable"/> on the <see cref="ModuleCatalog"/>.
/// </summary>
public void Run()
{
this.ModuleCatalog.Initialize();
this.LoadModulesWhenAvailable();
}
/// <summary>
/// Loads and initializes the module on the <see cref="IModuleCatalog"/> with the name <paramref name="moduleName"/>.
/// </summary>
/// <param name="moduleName">Name of the module requested for initialization.</param>
public void LoadModule(string moduleName)
{
var module = this.ModuleCatalog.Modules.Where(m => m.ModuleName == moduleName);
if (module == null || module.Count() != 1)
{
throw new ModuleNotFoundException(moduleName, string.Format(CultureInfo.CurrentCulture, Resources.ModuleNotFound, moduleName));
}
var modulesToLoad = this.ModuleCatalog.CompleteListWithDependencies(module);
this.LoadModuleTypes(modulesToLoad);
}
/// <summary>
/// Checks if the module needs to be retrieved before it's initialized.
/// </summary>
/// <param name="moduleInfo">Module that is being checked if needs retrieval.</param>
/// <returns></returns>
protected virtual bool ModuleNeedsRetrieval(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (moduleInfo.State == ModuleState.NotStarted)
{
// If we can instantiate the type, that means the module's assembly is already loaded into
// the AppDomain and we don't need to retrieve it.
bool isAvailable = Type.GetType(moduleInfo.ModuleType) != null;
if (isAvailable)
{
moduleInfo.State = ModuleState.ReadyForInitialization;
}
return !isAvailable;
}
return false;
}
private void LoadModulesWhenAvailable()
{
var whenAvailableModules = this.ModuleCatalog.Modules.Where(m => m.InitializationMode == InitializationMode.WhenAvailable);
var modulesToLoadTypes = this.ModuleCatalog.CompleteListWithDependencies(whenAvailableModules);
if (modulesToLoadTypes != null)
{
this.LoadModuleTypes(modulesToLoadTypes);
}
}
private void LoadModuleTypes(IEnumerable<IModuleInfo> moduleInfos)
{
if (moduleInfos == null)
{
return;
}
foreach (var moduleInfo in moduleInfos)
{
if (moduleInfo.State == ModuleState.NotStarted)
{
if (this.ModuleNeedsRetrieval(moduleInfo))
{
this.BeginRetrievingModule(moduleInfo);
}
else
{
moduleInfo.State = ModuleState.ReadyForInitialization;
}
}
}
this.LoadModulesThatAreReadyForLoad();
}
/// <summary>
/// Loads the modules that are not initialized and have their dependencies loaded.
/// </summary>
protected virtual void LoadModulesThatAreReadyForLoad()
{
bool keepLoading = true;
while (keepLoading)
{
keepLoading = false;
var availableModules = this.ModuleCatalog.Modules.Where(m => m.State == ModuleState.ReadyForInitialization);
foreach (var moduleInfo in availableModules)
{
if ((moduleInfo.State != ModuleState.Initialized) && (this.AreDependenciesLoaded(moduleInfo)))
{
moduleInfo.State = ModuleState.Initializing;
this.InitializeModule(moduleInfo);
keepLoading = true;
break;
}
}
}
}
private void BeginRetrievingModule(IModuleInfo moduleInfo)
{
var moduleInfoToLoadType = moduleInfo;
IModuleTypeLoader moduleTypeLoader = this.GetTypeLoaderForModule(moduleInfoToLoadType);
moduleInfoToLoadType.State = ModuleState.LoadingTypes;
// Delegate += works differently between SL and WPF.
// We only want to subscribe to each instance once.
if (!this.subscribedToModuleTypeLoaders.Contains(moduleTypeLoader))
{
moduleTypeLoader.ModuleDownloadProgressChanged += this.IModuleTypeLoader_ModuleDownloadProgressChanged;
moduleTypeLoader.LoadModuleCompleted += this.IModuleTypeLoader_LoadModuleCompleted;
this.subscribedToModuleTypeLoaders.Add(moduleTypeLoader);
}
moduleTypeLoader.LoadModuleType(moduleInfo);
}
private void IModuleTypeLoader_ModuleDownloadProgressChanged(object sender, ModuleDownloadProgressChangedEventArgs e)
{
this.RaiseModuleDownloadProgressChanged(e);
}
private void IModuleTypeLoader_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
if (e.Error == null)
{
if ((e.ModuleInfo.State != ModuleState.Initializing) && (e.ModuleInfo.State != ModuleState.Initialized))
{
e.ModuleInfo.State = ModuleState.ReadyForInitialization;
}
// This callback may call back on the UI thread, but we are not guaranteeing it.
// If you were to add a custom retriever that retrieved in the background, you
// would need to consider dispatching to the UI thread.
this.LoadModulesThatAreReadyForLoad();
}
else
{
this.RaiseLoadModuleCompleted(e);
// If the error is not handled then I log it and raise an exception.
if (!e.IsErrorHandled)
{
this.HandleModuleTypeLoadingError(e.ModuleInfo, e.Error);
}
}
}
/// <summary>
/// Handles any exception occurred in the module typeloading process,
/// and throws a <see cref="ModuleTypeLoadingException"/>.
/// This method can be overridden to provide a different behavior.
/// </summary>
/// <param name="moduleInfo">The module metadata where the error happened.</param>
/// <param name="exception">The exception thrown that is the cause of the current error.</param>
/// <exception cref="ModuleTypeLoadingException"></exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1")]
protected virtual void HandleModuleTypeLoadingError(IModuleInfo moduleInfo, Exception exception)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (!(exception is ModuleTypeLoadingException moduleTypeLoadingException))
{
moduleTypeLoadingException = new ModuleTypeLoadingException(moduleInfo.ModuleName, exception.Message, exception);
}
throw moduleTypeLoadingException;
}
private bool AreDependenciesLoaded(IModuleInfo moduleInfo)
{
var requiredModules = this.ModuleCatalog.GetDependentModules(moduleInfo);
if (requiredModules == null)
{
return true;
}
int notReadyRequiredModuleCount =
requiredModules.Count(requiredModule => requiredModule.State != ModuleState.Initialized);
return notReadyRequiredModuleCount == 0;
}
private IModuleTypeLoader GetTypeLoaderForModule(IModuleInfo moduleInfo)
{
foreach (IModuleTypeLoader typeLoader in this.ModuleTypeLoaders)
{
if (typeLoader.CanLoadModuleType(moduleInfo))
{
return typeLoader;
}
}
throw new ModuleTypeLoaderNotFoundException(moduleInfo.ModuleName, string.Format(CultureInfo.CurrentCulture, Resources.NoRetrieverCanRetrieveModule, moduleInfo.ModuleName), null);
}
private void InitializeModule(IModuleInfo moduleInfo)
{
if (moduleInfo.State == ModuleState.Initializing)
{
this.moduleInitializer.Initialize(moduleInfo);
moduleInfo.State = ModuleState.Initialized;
this.RaiseLoadModuleCompleted(moduleInfo, null);
}
}
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <remarks>Calls <see cref="Dispose(bool)"/></remarks>.
/// <filterpriority>2</filterpriority>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the associated <see cref="IModuleTypeLoader"/>s.
/// </summary>
/// <param name="disposing">When <see langword="true"/>, it is being called from the Dispose method.</param>
protected virtual void Dispose(bool disposing)
{
foreach (IModuleTypeLoader typeLoader in this.ModuleTypeLoaders)
{
if (typeLoader is IDisposable disposableTypeLoader)
{
disposableTypeLoader.Dispose();
}
}
}
#endregion
}
}
modulemanager在构造函数中包含了两个部分,IModuleInitializer接口和IModuleCatalog接口(上文已介绍过)
IModuleInitializer接口
该接口的定义:
/// <summary>
/// Declares a service which initializes the modules into the application.
/// </summary>
public interface IModuleInitializer
{
/// <summary>
/// Initializes the specified module.
/// </summary>
/// <param name="moduleInfo">The module to initialize</param>
void Initialize(IModuleInfo moduleInfo);
}
可以看出是通过传入moduleinfo参数实现对module的初始化
具体实现过程如下:
using System;
using System.Globalization;
using Prism.Ioc;
namespace Prism.Modularity
{
/// <summary>
/// Implements the <see cref="IModuleInitializer"/> interface. Handles loading of a module based on a type.
/// </summary>
public class ModuleInitializer : IModuleInitializer
{
private readonly IContainerExtension _containerExtension;
/// <summary>
/// Initializes a new instance of <see cref="ModuleInitializer"/>.
/// </summary>
/// <param name="containerExtension">The container that will be used to resolve the modules by specifying its type.</param>
public ModuleInitializer(IContainerExtension containerExtension)
{
this._containerExtension = containerExtension ?? throw new ArgumentNullException(nameof(containerExtension));
}
/// <summary>
/// Initializes the specified module.
/// </summary>
/// <param name="moduleInfo">The module to initialize</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Catches Exception to handle any exception thrown during the initialization process with the HandleModuleInitializationError method.")]
public void Initialize(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
IModule moduleInstance = null;
try
{
moduleInstance = this.CreateModule(moduleInfo);
if (moduleInstance != null)
{
moduleInstance.RegisterTypes(_containerExtension);
moduleInstance.OnInitialized(_containerExtension);
}
}
catch (Exception ex)
{
this.HandleModuleInitializationError(
moduleInfo,
moduleInstance?.GetType().Assembly.FullName,
ex);
}
}
/// <summary>
/// Handles any exception occurred in the module Initialization process,
/// This method can be overridden to provide a different behavior.
/// </summary>
/// <param name="moduleInfo">The module metadata where the error happened.</param>
/// <param name="assemblyName">The assembly name.</param>
/// <param name="exception">The exception thrown that is the cause of the current error.</param>
/// <exception cref="ModuleInitializeException"></exception>
public virtual void HandleModuleInitializationError(IModuleInfo moduleInfo, string assemblyName, Exception exception)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
if (exception == null)
throw new ArgumentNullException(nameof(exception));
Exception moduleException;
if (exception is ModuleInitializeException)
{
moduleException = exception;
}
else
{
if (!string.IsNullOrEmpty(assemblyName))
{
moduleException = new ModuleInitializeException(moduleInfo.ModuleName, assemblyName, exception.Message, exception);
}
else
{
moduleException = new ModuleInitializeException(moduleInfo.ModuleName, exception.Message, exception);
}
}
throw moduleException;
}
/// <summary>
/// Uses the container to resolve a new <see cref="IModule"/> by specifying its <see cref="Type"/>.
/// </summary>
/// <param name="moduleInfo">The module to create.</param>
/// <returns>A new instance of the module specified by <paramref name="moduleInfo"/>.</returns>
protected virtual IModule CreateModule(IModuleInfo moduleInfo)
{
if (moduleInfo == null)
throw new ArgumentNullException(nameof(moduleInfo));
return this.CreateModule(moduleInfo.ModuleType);
}
/// <summary>
/// Uses the container to resolve a new <see cref="IModule"/> by specifying its <see cref="Type"/>.
/// </summary>
/// <param name="typeName">The type name to resolve. This type must implement <see cref="IModule"/>.</param>
/// <returns>A new instance of <paramref name="typeName"/>.</returns>
protected virtual IModule CreateModule(string typeName)
{
Type moduleType = Type.GetType(typeName);
if (moduleType == null)
{
throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
}
return (IModule)_containerExtension.Resolve(moduleType);
}
}
}
核心是根据当前moduleinfo构造出一个Imodule对象,使用的时候需要调用imodule中定义的RegisterTypes和OnInitialized方法。如下所示:
leonablythe: 这个应该看具体设备,usb 串口之类的应该不需要配置可以直接检测到,网络设备肯定是需要根据设备ip 对本机ip 地址进行修改配置的,一般保证ip 前三个地址一致就可以了
weixin_51279799: 你好 请问一下 编程完以后需要在NIMAX中进行一些配置吗比如配置ip什么的 怎么配置呢
CSDN-Ada助手: 恭喜您写了第15篇博客!标题为“关于指针的一些注意点(持续记录)”,看起来很有深度和实用性。在这篇博客中,您提到了一些关于指针的注意事项,这对于学习和使用指针的人来说无疑是非常有帮助的。 我想说,您的持续创作态度非常值得称赞。通过记录自己学习的过程和经验,不仅可以帮助他人解决问题,同时也能够加深自己的理解和记忆。我期待着您未来更多关于指针的文章,或者是其他与编程相关的主题。或许您可以分享一些指针在实际项目中的应用案例,或者是与指针相关的高级技巧和技术。 谦虚而持续地创作下去,我相信您的博客会越来越受到读者的喜爱和关注。加油!
leonablythe: 我觉得如果等全部生成的话慢是一定的,可以在显示刷新上做手脚,比如初次只加载可视部分,其余部分可以分线程分批加载,至少在观感上会舒适一点?和我这个应该是类似的道理
cj5035: 楼主这篇是解决在已有控件上数据刷新问题嘛?有没有遇到过大批量控件生成缓慢的问题?控件是单线程动态new出来,不能复用的情况