Al actualizar el código de mi UI (C# en una aplicación .NET 4.0), me encontré con un bloqueo extraño debido a una llamada a la interfaz de usuario que se estaba ejecutando en el hilo equivocado. Sin embargo, ya estaba invocando esa llamada en el hilo principal, por lo que el bloqueo no tenía sentido: MainThreadDispatcher.Invoke(new Action(View.Method))
se colgó con "El hilo que llama no puede acceder a este objeto porque lo posee un hilo diferente". en la propiedad Ver.Usando el grupo de métodos C# ejecuta el código
Tras una investigación más profunda encontré la causa: estaba invocando a través de un grupo de métodos. Pensé que usar un grupo de métodos o un delegado/lambda son esencialmente lo mismo (ver también this question y this question). En su lugar, la conversión del grupo de métodos a un delegado hace que se ejecute el código, verificando el valor de View
. Esto se realiza de inmediato, es decir, en la secuencia original (no UI), que provocó el bloqueo. Si utilizo una lambda en su lugar, verificando la propiedad se hace más tarde, y por lo tanto en el hilo correcto.
Eso parece interesante, por decir lo menos. ¿Hay algún lugar en el estándar C# donde se menciona esto? ¿O eso está implícito debido a la necesidad de encontrar la conversión correcta?
Aquí hay un programa de prueba. Primero, la manera directa. En segundo lugar, en dos pasos, que muestra mejor lo que sucede. Para mayor diversión, modifico Item
después de que se haya creado el delegado.
namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication
{
using System.Threading;
using System.Windows.Threading;
using System;
static class Program
{
static Dispatcher mainDispatcher;
static void Main()
{
mainDispatcher = Dispatcher.CurrentDispatcher;
mainDispatcher.Thread.Name = "Main thread";
var childThread = new Thread(() =>
{
Console.WriteLine("--- Method group ---");
mainDispatcher.Invoke(new Action(Item.DoSomething));
Console.WriteLine("\n--- Lambda ---");
mainDispatcher.Invoke(new Action(() => Item.DoSomething()));
Console.WriteLine("\n--- Method group (two steps) ---");
var action = new Action(Item.DoSomething);
Console.WriteLine("Invoking");
mainDispatcher.Invoke(action);
Console.WriteLine("\n--- Lambda (two steps) ---");
action = new Action(() => Item.DoSomething());
Console.WriteLine("Invoking");
mainDispatcher.Invoke(action);
Console.WriteLine("\n--- Method group (modifying Item) ---");
action = new Action(Item.DoSomething);
item = null;
mainDispatcher.Invoke(action);
item = new UIItem();
Console.WriteLine("\n--- Lambda (modifying Item) ---");
action = new Action(() => Item.DoSomething());
item = null;
Console.WriteLine("Invoking");
mainDispatcher.Invoke(action);
mainDispatcher.InvokeShutdown();
});
childThread.Name = "Child thread";
childThread.Start();
Dispatcher.Run();
}
static UIItem item = new UIItem();
static UIItem Item
{
get
{
// mainDispatcher.VerifyAccess(); // Uncomment for crash.
Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
return item;
}
}
private class UIItem
{
public void DoSomething()
{
Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
}
}
}
}
versión corta:
namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication
{
using System.Threading;
using System.Windows.Threading;
using System;
static class Program
{
static Dispatcher mainDispatcher;
static void Main()
{
mainDispatcher = Dispatcher.CurrentDispatcher;
mainDispatcher.Thread.Name = "Main thread";
var childThread = new Thread(() =>
{
Console.WriteLine("--- Method group ---");
mainDispatcher.Invoke(new Action(Item.DoSomething));
Console.WriteLine("\n--- Lambda ---");
mainDispatcher.Invoke(new Action(() => Item.DoSomething()));
mainDispatcher.InvokeShutdown();
});
childThread.Name = "Child thread";
childThread.Start();
Dispatcher.Run();
}
static UIItem item = new UIItem();
static UIItem Item
{
get
{
mainDispatcher.VerifyAccess();
Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
return item;
}
}
private class UIItem
{
public void DoSomething()
{
Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
}
}
}
}
¿Qué llamada falla? ¿Cuál es la pila de llamadas? – SLaks
Elimine la línea comentada con VerifyAccess(), y verá que todas las llamadas que usan grupos de métodos fallan, ya que se accede a la propiedad Item en el subproceso secundario. –
Un programa corto pero completo * solo * que muestre la llamada problemática sería realmente útil. –