Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este artículo se proporciona información que podría resultarle útil si está escribiendo sus propios visualizadores de datos personalizados, especialmente si el objeto que se está visualizando o la propia interfaz de usuario del visualizador son complejos.
Los ejemplos siguientes se basan en una solución de Visual Studio que tiene dos proyectos. El primero se corresponde con un proyecto de .NET Framework 4.7.2 que es el componente del lado depurador para la lógica de la interfaz de usuario. El segundo es un proyecto de .NET Standard 2.0 que es el componente del lado depurado para que se pueda usar en aplicaciones de .NET Core.
El lado depurador consta de una ventana de WPF que podría contener un control ProgressBar
indeterminado visible en la carga y dos etiquetas denominadas DataLabel
y ErrorLabel
, ambas contraídas durante la carga. Una vez que termine de capturar los datos del objeto que intenta visualizar, la barra de progreso se contraerá y el visualizador mostrará el rótulo de datos con la información pertinente. Si se produce un error, la barra de progreso también estará oculta, pero mostrará un mensaje de error con la etiqueta de error. A continuación encontrará un ejemplo simplificado:
<Window x:Class="AdvancedVisualizer.DebuggerSide.VisualizerDialog"
xmlns:local="clr-namespace:AdvancedVisualizer.DebuggerSide">
<Grid>
<StackPanel x:Name="progressControl" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<ProgressBar IsIndeterminate="True" Width="200" Height="10"/>
<Label HorizontalAlignment="Center">Loading...</Label>
</StackPanel>
<Label x:Name="DataLabel" Visibility="Collapsed" HorizontalAlignment="Center" VerticalAlignment="Center" />
<Label x:Name="ErrorLabel" Visibility="Collapsed" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
Para simplificar el ejemplo, la lógica de interacción de VisualizerDialog
tiene un constructor sencillo que registra un controlador de eventos en su evento Loaded
. Este controlador de eventos se encarga de capturar los datos y cambia en función de cada ejemplo, por lo que se muestra por separado en cada sección.
public partial class VisualizerDialog : Window
{
private AdvancedVisualizerViewModel ViewModel => (AdvancedVisualizerViewModel)this.DataContext;
public VisualizerDialog()
{
InitializeComponent();
this.Loaded += VisualizerLoaded;
}
public void VisualizerLoaded(object sender, RoutedEventArgs e)
{
// Logic to fetch and show the data in the UI.
}
}
El lado depurador tiene un modelo de vista denominado AdvancedVisualizerViewModel
para controlar la lógica del visualizador para capturar sus datos del lado depurado. Esto cambia en función de cada ejemplo, por lo que se muestra por separado en cada sección. Por último, el punto de entrada del visualizador se muestra de la manera siguiente:
[assembly: DebuggerVisualizer(typeof(AdvancedVisualizer.DebuggerSide.AdvancedVisualizer), typeof(AdvancedVisualizer.DebuggeeSide.CustomVisualizerObjectSource), Target = typeof(VerySlowObject), Description = "Very Slow Object Visualizer")]
namespace AdvancedVisualizer.DebuggerSide
{
public class AdvancedVisualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
IAsyncVisualizerObjectProvider asyncObjectProvider = (IAsyncVisualizerObjectProvider)objectProvider;
AdvancedVisualizerViewModel viewModel = new AdvancedVisualizerViewModel(asyncObjectProvider);
Window advancedVisualizerWindow = new VisualizerDialog() { DataContext = viewModel };
advancedVisualizerWindow.ShowDialog();
}
}
}
Nota:
En el código anterior, estamos realizando una conversión en objectProvider
. La razón de esta conversión se explica en la sección Uso de la nueva API asincrónica.
El lado depurado varía en función del ejemplo, por lo que se muestra por separado en cada sección.
Uso de la nueva API asincrónica
Por motivos de compatibilidad, el método Show
que DialogDebuggerVisualizer
sobrescribe sigue recibiendo una instancia del proveedor de objetos de tipo IVisualizerObjectProvider
. Aun así, este tipo también implementa la interfaz IAsyncVisualizerObjectProvider
. Por lo tanto, es seguro convertirlo al usar VS 2022 versión 17.2 y posteriores. Ese proveedor agrega una implementación asincrónica de los métodos presentes en IVisualizerObjectProvider2
.
Control del tiempo de serialización largo
En algunos casos, al llamar al método GetDeserializableObjectAsync predeterminado en IAsyncVisualizerObjectProvider, el visualizador producirá una excepción de tiempo de espera. Las operaciones del visualizador de datos personalizado solo se permiten durante un máximo de cinco segundos para garantizar que Visual Studio siga respondiendo. Es decir, cada llamada a GetDeserializableObjectAsync, ReplaceDataAsync, TransferDeserializableObjectAsync, etc., debe finalizar su ejecución antes de que se alcance el límite de tiempo; de lo contrario, VS generará una excepción. Dado que no hay ningún plan para que se permita cambiar esta restricción de tiempo, las implementaciones del visualizador deben controlar los casos en los que un objeto tarda más de cinco segundos en serializarse. Para controlar este escenario correctamente, se recomienda que el visualizador controle el proceso de pasar datos del componente del lado depurado al componente del lado depurador por fragmentos o partes.
Nota:
Los proyectos desde los que se obtuvieron estos fragmentos de código se pueden descargar desde el repositorio VSSDK-Extensibility-Samples.
Por ejemplo, imagine que tiene un objeto complejo denominado VerySlowObject
con muchos campos y propiedades que se deben procesar y copiar en el componente del visualizador del lado depurador. Entre esas propiedades se encuentra VeryLongList
, que, en función de la instancia de VerySlowObject
, puede serializarse dentro del plazo de cinco segundos o tardar un poco más.
public class VerySlowObject
{
// More properties...
// One of the properties we want to visualize.
public List<SomeRandomObject> VeryLongList { get; }
// More properties...
}
Por esta razón, necesita crear su propio componente del lado depurado, que es una clase que se deriva de VisualizerObjectSource e invalida el método TransferData.
public class CustomVisualizerObjectSource : VisualizerObjectSource
{
public override void TransferData(object obj, Stream fromVisualizer, Stream toVisualizer)
{
// Serialize `obj` into the `toVisualizer` stream...
}
}
En este punto, tiene dos alternativas: puede agregar tipos personalizados "Command" y "Response", que permiten al visualizador coordinar entre ambos componentes en el estado de la transferencia de datos, o bien puede dejar que VisualizerObjectSource lo controle por su cuenta. Si el objeto solo tuviera una colección simple de los mismos tipos (y si usted quisiera enviar todos los elementos a la interfaz de usuario), se recomendaría la segunda opción, ya que el lado depurado simplemente devolvería segmentos de la colección hasta que se alcanzase el final. En el caso de que tuviera varias partes diferentes o simplemente quisiera devolver una parte del objeto, probablemente la primera opción sería más fácil. Si optase por el segundo enfoque, crearía las clases siguientes en el proyecto del lado depurado.
[Serializable]
public class GetVeryLongListCommand
{
public int StartOffset { get; }
// Constructor...
}
[Serializable]
public class GetVeryLongListResponse
{
public string[] Values { get; }
public bool IsComplete { get; }
// Constructor...
}
Con las clases auxiliares implementadas, el modelo de vista puede tener un método asincrónico para capturar los datos y procesarlos para que se muestren en la interfaz de usuario. En este ejemplo, llámelo GetDataAsync
.
public async Task<string> GetDataAsync()
{
List<string> verySlowObjectList = new List<string>();
// Consider the possibility that we might timeout when fetching the data.
bool isRequestComplete;
do
{
// Send the command requesting more elements from the collection and process the response.
IDeserializableObject deserializableObject = await m_asyncObjectProvider.TransferDeserializableObjectAsync(new GetVeryLongListCommand(verySlowObjectList.Count), CancellationToken.None);
GetVeryLongListResponse response = deserializableObject.ToObject<GetVeryLongListResponse>();
// Check if a timeout occurred. If it did we try fetching more data again.
isRequestComplete = response.IsComplete;
// If no timeout occurred and we did not get all the elements we asked for, then we reached the end
// of the collection and we can safely exit the loop.
verySlowObjectList.AddRange(response.Values);
}
while (!isRequestComplete);
// Do some processing of the data before showing it to the user.
string valuesToBeShown = ProcessList(verySlowObjectList);
return valuesToBeShown;
}
private string ProcessList(List<string> verySlowObjectList)
{
// Do some processing of the data before showing it to the user...
}
El método GetDataAsync
creará la instancia GetVeryLongListCommand
en una repetición, la enviará al lado depurado para que la procese y, en función de la respuesta, la reenviará para obtener el resto de los datos o finalizar el ciclo, dado que ya lo ha capturado todo. El método TransferData
del lado depurado puede controlar la solicitud como se indica a continuación.
public override void TransferData(object obj, Stream fromVisualizer, Stream toVisualizer)
{
// Serialize `obj` into the `toVisualizer` stream...
// Start the timer so that we can stop processing the request if it's are taking too long.
long startTime = Environment.TickCount;
if (obj is VerySlowObject slowObject)
{
bool isComplete = true;
// Read the supplied command
fromVisualizer.Seek(0, SeekOrigin.Begin);
IDeserializableObject deserializableObject = GetDeserializableObject(fromVisualizer);
GetVeryLongListCommand command = deserializableObject.ToObject<GetVeryLongListCommand>();
List<string> returnValues = new List<string>();
for (int i = (int)command.StartOffset; i < slowObject.VeryLongList?.Count; i++)
{
// If the call takes more than 3 seconds, just return what we have received so far and fetch the remaining data on a posterior call.
if ((Environment.TickCount - startTime) > 3_000)
{
isComplete = false;
break;
}
// This call takes a considerable amount of time...
returnValues.Add(slowObject.VeryLongList[i].ToString());
}
GetVeryLongListResponse response = new GetVeryLongListResponse(returnValues.ToArray(), isComplete);
Serialize(toVisualizer, response);
}
else
{
// Handle failure case...
}
}
Cuando el modelo de vista tenga todos los datos, el controlador de eventos VisualizerLoaded
del visualizador realizará la llamada al modelo de vista para que pueda solicitar los datos.
public void VisualizerLoaded(object sender, RoutedEventArgs e)
{
_ = Dispatcher.InvokeAsync(async () =>
{
try
{
string data = await this.ViewModel.GetDataAsync();
this.DataLabel.Visibility = Visibility.Visible;
this.DataLabel.Content = data;
}
catch
{
this.ErrorLabel.Content = "Error getting data.";
}
finally
{
this.progressControl.Visibility = Visibility.Collapsed;
}
});
}
Nota
Es importante controlar los errores que se produzcan con la solicitud e informar de ellos al usuario.
Con estos cambios, el visualizador debería poder controlar objetos que tardan mucho en serializarse del lado depurado al lado del depurador.