Каков наилучший подход для достижения уникальности объекта, совместно используемого несколькими потоками?


Я заинтересован в синхронизации моих вызовов функций к базе данных + другие функции, чтобы построить некоторые метрики для производительности моего приложения. Я использовал секундомер и объект metrics, но он, похоже, не всегда дает правильные значения. Иногда время, затраченное на вызов функции, совершенно одинаково для всех вызовов, что нереально...

Я обнаружил, что причина проблемы связана со значениями свойств объекта метрики. Значения одного объекта метрики получают перезаписывается, когда другим экземплярам метрик, созданных другими потоками, присваиваются значения. Похоже, что значения свойств задаются для каждой ссылки, хотя каждый поток создает новый экземпляр.

Каков наилучший подход для достижения уникальности объекта, разделяемого несколькими потоками?

Код ниже:

private Metrics Metrics;
private Stopwatch Stopwatch;
private int DegreeOfParallelism { get { return Convert.ToInt32(ConfigurationManager.AppSettings["DegreeOfParallelism"].ToString()); } }

var lOptions = new ParallelOptions() { MaxDegreeOfParallelism = DegreeOfParallelism };
Parallel.ForEach(RequestBag, lOptions, (lItem, loopState) =>
{
    if (!string.IsNullOrEmpty(lItem.XmlRequest))
    {
        try
        {
            Metrics = new Metrics();
            Stopwatch = new Stopwatch();
            Stopwatch.Start();
            ObjRef = new Object();
            lItem.XmlRequest = ObjRef.GetDecision(Username, Password);
            Stopwatch.Stop();
            Metrics.ElapsedTime = string.Format("{0:0.00}", Stopwatch.Elapsed.TotalSeconds);

            Stopwatch.Restart();
            if (!string.IsNullOrEmpty(DBConnectionString))
            {
                DataAccess = new DataAccess2(DBConnectionString);
                DataAccess.WriteToDB(lItem.XmlRequest);  
            }
            Stopwatch.Stop();
            Metrics.DbFuncCallTime = string.Format("{0:0.00}", Stopwatch.Elapsed.TotalSeconds); 
        }
        catch (Exception pEx)
        { 
            KeepLog(pEx);
            Metrics.HasFailed = true;
        }
        finally
        {
            ProcessedIdsBag.Add(lItem.OrderId);
            Metrics.ProcessedOrderId = lItem.OrderId;
            Metrics.DegreeOfParallelism = DegreeOfParallelism;
            Metrics.TotalNumOfOrders = NumberOfOrders;
            Metrics.TotalNumOfOrdersProcessed = ProcessedIdsBag.Count;
            pBackgroundWorker.ReportProgress(Metrics.GetProgressPercentage(NumberOfOrders, ProcessedIdsBag.Count), Metrics);

            RequestBag.TryTake(out lItem);
        }
    }
});
Любая помощь будет очень признательна. Спасибо, R
2 2

2 ответа:

Похоже, что вы хотите создать объект метрики для каждой итерации, а затем агрегировать их в конце:

private ConcurrentBag<Metrics> allMetrics = new ConcurrentBag<Metrics>();
private int DegreeOfParallelism { get { return Convert.ToInt32(ConfigurationManager.AppSettings["DegreeOfParallelism"].ToString()); } }

var lOptions = new ParallelOptions() { MaxDegreeOfParallelism = DegreeOfParallelism };
Parallel.ForEach(RequestBag, lOptions, (lItem, loopState) =>
{
    if (!string.IsNullOrEmpty(lItem.XmlRequest))
    {
        try
        {
            var Metrics = new Metrics();
            var Stopwatch = new Stopwatch();
            Stopwatch.Start();
            ObjRef = new Object();
            lItem.XmlRequest = ObjRef.GetDecision(Username, Password);
            Stopwatch.Stop();
            Metrics.ElapsedTime = string.Format("{0:0.00}", Stopwatch.Elapsed.TotalSeconds);

            Stopwatch.Restart();
            if (!string.IsNullOrEmpty(DBConnectionString))
            {
                DataAccess = new DataAccess2(DBConnectionString);
                DataAccess.WriteToDB(lItem.XmlRequest);  
            }
            Stopwatch.Stop();
            Metrics.DbFuncCallTime = string.Format("{0:0.00}", Stopwatch.Elapsed.TotalSeconds); 
        }
        catch (Exception pEx)
        { 
            KeepLog(pEx);
            Metrics.HasFailed = true;
        }
        finally
        {
            ProcessedIdsBag.Add(lItem.OrderId);
            Metrics.ProcessedOrderId = lItem.OrderId;
            Metrics.DegreeOfParallelism = DegreeOfParallelism;
            Metrics.TotalNumOfOrders = NumberOfOrders;
            Metrics.TotalNumOfOrdersProcessed = ProcessedIdsBag.Count;
            pBackgroundWorker.ReportProgress(Metrics.GetProgressPercentage(NumberOfOrders, ProcessedIdsBag.Count), Metrics);

            RequestBag.TryTake(out lItem);
            allMetrics.add(Metrics);
        }
    }
});

// Aggregate everything in AllMetrics here

Вам необходимо изменить область действия секундомера и переменных метрик.

В настоящее время каждый поток использует одну и ту же переменную метрики. Как только поток входит в блок try, он создает новый экземпляр метрики (правильно), но помещает его в общую переменную (неправильно). Все остальные потоки будут видеть этот новый экземпляр при чтении общей переменной, пока не появится следующий поток и не начнет весь процесс заново.

Перемещение

private Metrics Metrics;
private Stopwatch Stopwatch;

Чтобы просто внутри вашего петля

Parallel.ForEach(RequestBag, lOptions, (lItem, loopState) =>
{
    private Metrics Metrics;
    private Stopwatch Stopwatch;
...

Это даст каждой итерации через цикл его собственную переменную, в которой будет храниться его собственный экземпляр объекта.