EndInvoke меняет текущий CallContext - почему? - PullRequest
6 голосов
/ 19 мая 2009

У меня есть следующий тест

[Test]
public void aaa()
{
    CallContext.LogicalSetData("aa", "1");

    Action parallelMethod = () => CallContext.LogicalSetData("aa", "2"); 
    var r = parallelMethod.BeginInvoke(null, null); 
    parallelMethod.EndInvoke(r);

    Assert.That(CallContext.LogicalGetData("aa"), Is.EqualTo("1")); 
}

Может кто-нибудь сказать мне, почему этот тест не проходит в последней строке?

На самом деле я знаю почему - потому что EndInvoke объединяет CallContext из метода paralell с текущим - но я не понимаю причину этого.

Для меня это поведение похоже на изменение значений параметров метода изнутри метода, который вызывается: - (

РЕДАКТИРОВАТЬ: Я изменил мой пример кода, чтобы использовать только LogicalGetData и LogicalSetData. Как вы можете видеть в моем другом вопросе Я хочу передать некоторые данные в другой поток, но я не ожидал, что EndInvoke () заменит мои значения значениями, измененными в другом потоке.

Ответы [ 2 ]

7 голосов
/ 24 сентября 2009

Поведение, проиллюстрированное вашим примером, действительно задумано. LogicalCallContext может проходить в двух направлениях через асинхронный вызов или удаленный вызов .net. Когда вы вызываете EndInvoke, LogicalCallContext дочернего контекста объединяется обратно с родительским, как вы заметили. Это сделано намеренно, чтобы вызывающие удаленные методы могли получить доступ к любым значениям, установленным удаленным методом. Вы можете использовать эту функцию для передачи данных назад от ребенка, если хотите.

Отладка с помощью пошагового перехода на .NET Framework, есть явные комментарии на этот счет:

в System.Runtime.Remoting.Proxies.RemotingProxy.Invoke:

    case Message.EndAsync: 
         // This will also merge back the call context
         // onto the thread that called EndAsync
         RealProxy.EndInvokeHelper(m, false);

в System.Runtime.Remoting.Proxies.RealProxy.EndInvokeHelper:

    // Merge the call context back into the thread that
    // called EndInvoke 
    CallContext.GetLogicalCallContext().Merge(
         mrm.LogicalCallContext);

Если вы хотите избежать слияния данных, это довольно легко пропустить, просто избегайте вызова EndInvoke из основного потока. Например, вы можете использовать ThreadPool.QueueUserWorkItem, который будет передавать LogicalCallContext в , но не в него, или вызывать EndInvoke из AsyncCallback.

Если посмотреть на пример на сайте Microsoft Connect, причина того, что значение LogicalSetData не возвращается из вызова RunWorkerCompleted, заключается в том, что BackgroundWorker не передает контекст обратно. Кроме того, помните, что LogicalSetData - это не то же самое, что локальное хранилище потока, поэтому не имеет значения, что RunWorkerCompleted выполняется в потоке пользовательского интерфейса - LogicalCallContext все еще является дочерним контекстом, и если родительский процесс явно не передает его обратно вызывая EndInvoke из порождающего потока, он будет отменен. Если вы хотите локальное хранилище потока, вы можете получить к нему доступ из Thread, например, так:

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Thread.SetData(Thread.GetNamedDataSlot("foo"), "blah!!");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var val = (string)Thread.GetData(Thread.GetNamedDataSlot("foo"));
        MessageBox.Show(val ?? "no value");
    }

В этом примере выскакивает MessageBox с надписью "бла !!" Причина в том, что оба обратных вызова выполняются в потоке пользовательского интерфейса, поэтому имеют доступ к одному и тому же локальному хранилищу потока.

Надеюсь, это поможет прояснить ситуацию.

0 голосов
/ 19 мая 2009

Это потому, что вы смешиваете SetData / GetData с LogicalSetData / LogicalGetData. Есть статья , о которой вы могли бы узнать больше о различиях между этими двумя методами. Основное правило здесь - всегда использовать SetData в сочетании с GetData и LogicalSetData в сочетании с LogicalGetData.

Эта модификация сделает ваш тест успешным:

[Test]
public void aaa()
{
    CallContext.SetData("aa", "1");
    Action parallelMethod = () => CallContext.SetData("aa", "2");
    var r = parallelMethod.BeginInvoke(null, null);
    Assert.That(CallContext.GetData("aa"), Is.EqualTo("1"));
    parallelMethod.EndInvoke(r);
    Assert.That(CallContext.GetData("aa"), Is.EqualTo("1"));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...