2

假设我的 C# 程序中有任意数量的线程。每个线程都需要通过查找其历史来查找特定路径的变更集 ID。该方法如下所示:

public List<int> GetIdsFromHistory(string path, VersionControlServer tfsClient)
{ 
   IEnumerable submissions = tfsClient.QueryHistory(
      path,
      VersionSpec.Latest,
      0,
      RecursionType.None, // Assume that the path is to a file, not a directory
      null,
      null,
      null,
      Int32.MaxValue,
      false,
      false);

   List<int> ids = new List<int>();
   foreach(Changeset cs in submissions)
   {
      ids.Add(cs.ChangesetId);
   }
   return ids;
}

我的问题是,每个线程都需要它自己的VersionControlServer实例还是一个就足够了?我的直觉告诉我,每个线程都需要自己的实例,因为 TFS SDK 使用 Web 服务,如果我真的要获得并行行为,我可能应该打开多个连接。如果我只使用一个连接,我的直觉告诉我,即使我有多个线程,我也会得到串行行为。

如果我需要与线程一样多的实例,我会考虑使用对象池模式,但是如果不使用连接会超时并在很长一段时间内关闭吗?在这方面,文档似乎很少。

4

1 回答 1

1

使用 SAME 客户端的线程似乎是最快的选择。

这是一个测试程序的输出,该程序运行 4 次测试,每次 5 次,并以毫秒为单位返回平均结果。显然,跨多个线程使用同一个客户端是最快的执行:

Parallel Pre-Alloc: Execution Time Average (ms): 1921.26044
Parallel AllocOnDemand: Execution Time Average (ms): 1391.665
Parallel-SameClient: Execution Time Average (ms): 400.5484
Serial: Execution Time Average (ms): 1472.76138

作为参考,这里是测试程序本身(也在GitHub 上):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.Collections;
using System.Threading.Tasks;
using System.Diagnostics;

namespace QueryHistoryPerformanceTesting
{
    class Program
    {
        static string TFS_COLLECTION = /* TFS COLLECTION URL */
        static VersionControlServer GetTfsClient()
        {
            var projectCollectionUri = new Uri(TFS_COLLECTION);
            var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri, new UICredentialsProvider());
            projectCollection.EnsureAuthenticated();
            return projectCollection.GetService<VersionControlServer>();
        }

        struct ThrArg
        {
            public VersionControlServer tfc { get; set; }
            public string path { get; set; }
        }

        static List<string> PATHS = new List<string> {
            // ASSUME 21 FILE PATHS
        };

        static int NUM_RUNS = 5;
        static void Main(string[] args)
        {
            var results = new List<TimeSpan>();

            for (int i = NUM_RUNS; i > 0; i--)
            {
                results.Add(RunTestParallelPreAlloc());
            }
            Console.WriteLine("Parallel Pre-Alloc: Execution Time Average (ms): " + results.Select(t => t.TotalMilliseconds).Average());

            results.Clear();
            for (int i = NUM_RUNS; i > 0; i--)
            {
                results.Add(RunTestParallelAllocOnDemand());
            }
            Console.WriteLine("Parallel AllocOnDemand: Execution Time Average (ms): " + results.Select(t => t.TotalMilliseconds).Average());

            results.Clear();
            for (int i = NUM_RUNS; i > 0; i--)
            {
                results.Add(RunTestParallelSameClient());
            }
            Console.WriteLine("Parallel-SameClient: Execution Time Average (ms): " + results.Select(t => t.TotalMilliseconds).Average());

            results.Clear();
            for (int i = NUM_RUNS; i > 0; i--)
            {
                results.Add(RunTestSerial());
            }
            Console.WriteLine("Serial: Execution Time Average (ms): " + results.Select(t => t.TotalMilliseconds).Average());
        }

        static TimeSpan RunTestParallelPreAlloc()
        {
            var paths = new List<ThrArg>();
            paths.AddRange( PATHS.Select( x => new ThrArg { path = x, tfc = GetTfsClient() } ) );
            return RunTestParallel(paths);
        }

        static TimeSpan RunTestParallelAllocOnDemand()
        {
            var paths = new List<ThrArg>();
            paths.AddRange(PATHS.Select(x => new ThrArg { path = x, tfc = null }));
            return RunTestParallel(paths);
        }

        static TimeSpan RunTestParallelSameClient()
        {
            var paths = new List<ThrArg>();
            var _tfc = GetTfsClient();
            paths.AddRange(PATHS.Select(x => new ThrArg { path = x, tfc = _tfc }));
            return RunTestParallel(paths);
        }

        static TimeSpan RunTestParallel(List<ThrArg> args)
        {
            var allIds = new List<int>();

            var stopWatch = new Stopwatch();
            stopWatch.Start();
            Parallel.ForEach(args, s =>
            {
                allIds.AddRange(GetIdsFromHistory(s.path, s.tfc));
            }
            );
            stopWatch.Stop();

            return stopWatch.Elapsed;
        }

        static TimeSpan RunTestSerial()
        {
            var allIds = new List<int>();
            VersionControlServer tfsc = GetTfsClient();

            var stopWatch = new Stopwatch();
            stopWatch.Start();
            foreach (string s in PATHS)
            {
                allIds.AddRange(GetIdsFromHistory(s, tfsc));
            }
            stopWatch.Stop();

            return stopWatch.Elapsed;
        }

        static List<int> GetIdsFromHistory(string path, VersionControlServer tfsClient)
        {
            if (tfsClient == null)
            {
                tfsClient = GetTfsClient();
            }

            IEnumerable submissions = tfsClient.QueryHistory(
                path,
                VersionSpec.Latest,
                0,
                RecursionType.None, // Assume that the path is to a file, not a directory
                null,
                null,
                null,
                Int32.MaxValue,
                false,
                false);

            List<int> ids = new List<int>();
            foreach(Changeset cs in submissions)
            {
                ids.Add(cs.ChangesetId);
            }
            return ids;
        }
于 2013-01-24T17:36:46.057 回答