См. Этот вопрос для справочной информации:
Как задачи в параллельной библиотеке задач влияют на ActivityID?
Этот вопрос задает вопрос, как задачи влияют на Trace.CorrelationManager.ActivityId .@Greg Samson ответил на свой вопрос тестовой программой, показывающей, что ActivityId надежен в контексте задач.Тестовая программа устанавливает ActivityId в начале делегата Task, спит для имитации работы, затем проверяет ActivityId в конце, чтобы убедиться, что это то же самое значение (то есть, что оно не было изменено другим потоком).Программа работает успешно.
При исследовании других «контекстных» опций для потоков, задач и параллельных операций (в конечном счете, для обеспечения лучшего контекста для ведения журнала) я столкнулся со странной проблемой с Trace.CorrelationManager.LogicalOperationStack (мне все равно было странно).Я скопировал свой «ответ» на его вопрос ниже.
Я думаю, что он адекватно описывает проблему, с которой я столкнулся (Trace.CorrelationManager.LogicalOperationStack, по-видимому, поврежден - или что-то - при использовании в контексте Parallel.For, но только если сам Parallel.For заключен в логическую операцию).
Вот мои вопросы:
Должно ли Trace.CorrelationManager.LogicalOperationStack быть пригодным для использованияс Parallel.For?Если да, то должно ли это иметь значение, если логическая операция уже работает с Parallel.For?
Есть ли "правильный" способ использовать LogicalOperationStack с Parallel.For?Могу ли я по-разному кодировать этот пример программы, чтобы она «работала»?Под «работами» я подразумеваю, что LogicalOperationStack всегда имеет ожидаемое количество записей, а сами записи являются ожидаемыми записями.
Я провел дополнительное тестирование с использованием потоков Threads и ThreadPool,но я должен был бы вернуться и повторить эти тесты, чтобы увидеть, сталкивался ли я с подобными проблемами.
Я скажу, что кажется, что потоки Task / Parallel и ThreadPool «наследуют» Trace.CorrelationManager.Значения ActivityId и Trace.CorrelationManager.LogicalOperationStack из родительского потока.Это ожидается, поскольку эти значения хранятся в CorrelationManager с использованием метода LogicalSetData CallContext (в отличие от SetData).
Снова, пожалуйста, вернитесь к этому вопросу, чтобы получить исходный контекст для"ответ", который я разместил ниже:
Как задачи в параллельной библиотеке задач влияют на ActivityID?
См. также этот похожий вопрос (который до сих пор не былответил) на форуме Microsoft Parallel Extensions:
http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9
[НАЧАЛО ПАСТЫ]
Пожалуйста, прости меня за публикацию этого ответана самом деле это не ответ на ваш вопрос, однако, он связан с вашим вопросом, так как он касается поведения CorrelationManager и потоков / задач / и т. д.Я пытался использовать LogicalOperationStack
(и StartLogicalOperation/StopLogicalOperation
методы) CorrelationManager для обеспечения дополнительного контекста в многопоточных сценариях.
Я взял ваш пример и слегка его изменил, чтобы добавить возможность выполнять работу параллельно, используяParallel.For.Кроме того, я использую StartLogicalOperation/StopLogicalOperation
для скобки (внутри) DoLongRunningWork
.Концептуально, DoLongRunningWork
делает что-то подобное каждый раз, когда выполняется:
DoLongRunningWork
StartLogicalOperation
Thread.Sleep(3000)
StopLogicalOperation
Я обнаружил, что если я добавлю эти логические операции в ваш код (более или менее как есть), все логические операциисинхронизироваться (всегда ожидаемое количество операций в стеке, а значения операций в стеке всегда соответствуют ожидаемым).
В некоторых моих собственных испытаниях я обнаружил, что это не всегда так. Стек логической операции становился «поврежденным». Лучшее объяснение, которое я мог бы придумать, заключается в том, что «слияние» задней части информации CallContext с «родительским» потоком при выходе из «дочернего» потока приводило к тому, что «старая» информация о контексте дочернего потока (логическая операция) была « наследуется другим дочерним потоком одного брата.
Проблема также может быть связана с тем фактом, что Parallel.For, по-видимому, использует основной поток (по крайней мере, в коде примера, как написано) в качестве одного из «рабочих потоков» (или как там их следует вызывать в параллельном режиме). домен). Всякий раз, когда выполняется DoLongRunningWork, новая логическая операция запускается (в начале) и останавливается (в конце) (то есть помещается в LogicalOperationStack и откатывается назад от него). Если у основного потока уже есть действующая логическая операция, и если DoLongRunningWork выполняется В ОСНОВНОЙ РЕЗЬБЕ, то запускается новая логическая операция, поэтому в LogicalOperationStack основного потока теперь выполняется ДВА операции. Любые последующие исполнения DoLongRunningWork (пока эта «итерация» DoLongRunningWork выполняется в главном потоке) (по-видимому) будут наследовать LogicalOperationStack основного потока (который теперь имеет две операции, а не только одну ожидаемую операцию).
Мне потребовалось много времени, чтобы понять, почему поведение LogicalOperationStack в моем примере отличается от моего модифицированного варианта вашего примера. Наконец, я увидел, что в своем коде я заключил в скобки всю программу в виде логической операции, тогда как в моей модифицированной версии тестовой программы я этого не сделал. Подразумевается, что в моей тестовой программе каждый раз, когда выполнялась моя «работа» (аналог DoLongRunningWork), уже действовала логическая операция. В моей модифицированной версии вашей тестовой программы я не заключил в скобки всю программу в логической операции.
Итак, когда я изменил вашу тестовую программу, чтобы заключить в нее всю программу в логической операции И если я использую Parallel.For, я столкнулся с точно такой же проблемой.
Используя приведенную выше концептуальную модель, она будет успешно работать:
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
Хотя это в конечном итоге будет подтверждено из-за явно не синхронизированной LogicalOperationStack:
StartLogicalOperation
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
StopLogicalOperation
Вот мой пример программы. Он похож на ваш в том, что у него есть метод DoLongRunningWork, который управляет ActivityId, а также LogicalOperationStack. У меня также есть два варианта пинания DoLongRunningWork. В одном аромате используются задачи, в другом - Parallel.For. Каждый вариант также может быть выполнен так, что вся параллельная операция заключена в логическую операцию или нет. Таким образом, существует всего 4 способа выполнения параллельной операции. Чтобы попробовать каждый из них, просто раскомментируйте нужный метод «Использовать ...», перекомпилируйте и запустите. UseTasks
, UseTasks(true)
и UseParallelFor
должны выполняться до конца. UseParallelFor(true)
будет утверждаться в какой-то момент, потому что LogicalOperationStack не имеет ожидаемого количества записей.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace CorrelationManagerParallelTest
{
class Program
{
static void Main(string[] args)
{
//UseParallelFor(true) will assert because LogicalOperationStack will not have expected
//number of entries, all others will run to completion.
UseTasks(); //Equivalent to original test program with only the parallelized
//operation bracketed in logical operation.
////UseTasks(true); //Bracket entire UseTasks method in logical operation
////UseParallelFor(); //Equivalent to original test program, but use Parallel.For
//rather than Tasks. Bracket only the parallelized
//operation in logical operation.
////UseParallelFor(true); //Bracket entire UseParallelFor method in logical operation
}
private static List<int> threadIds = new List<int>();
private static object locker = new object();
private static int mainThreadId = Thread.CurrentThread.ManagedThreadId;
private static int mainThreadUsedInDelegate = 0;
// baseCount is the expected number of entries in the LogicalOperationStack
// at the time that DoLongRunningWork starts. If the entire operation is bracketed
// externally by Start/StopLogicalOperation, then baseCount will be 1. Otherwise,
// it will be 0.
private static void DoLongRunningWork(int baseCount)
{
lock (locker)
{
//Keep a record of the managed thread used.
if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))
threadIds.Add(Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId == mainThreadId)
{
mainThreadUsedInDelegate++;
}
}
Guid lo1 = Guid.NewGuid();
Trace.CorrelationManager.StartLogicalOperation(lo1);
Guid g1 = Guid.NewGuid();
Trace.CorrelationManager.ActivityId = g1;
Thread.Sleep(3000);
Guid g2 = Trace.CorrelationManager.ActivityId;
Debug.Assert(g1.Equals(g2));
//This assert, LogicalOperation.Count, will eventually fail if there is a logical operation
//in effect when the Parallel.For operation was started.
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Count == baseCount + 1, string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Count, baseCount + 1));
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Peek().Equals(lo1), string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Peek(), lo1));
Trace.CorrelationManager.StopLogicalOperation();
}
private static void UseTasks(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
task = Task.Factory.StartNew(() =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
private static void UseParallelFor(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Parallel.For(0, totalThreads, i =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
});
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
}
}
Весь этот вопрос о том, можно ли использовать LogicalOperationStack с Parallel.For (и / или другими конструкциями потоков / задач) или как его можно использовать, вероятно, заслуживает отдельного вопроса. Может быть, я отправлю вопрос. В то же время мне интересно, есть ли у вас какие-либо мысли по этому поводу (или, если вам интересно, вы рассматривали возможность использования LogicalOperationStack, поскольку ActivityId представляется безопасным).
[КОНЕЦ ПАСТЫ]
У кого-нибудь есть мысли по этому поводу?