单独的线程是否需要它自己的版本控制服务器实例才能与另一个实例同时工作

本文关键字:实例 另一个 工作 服务器 版本控制 是否 线程 自己的 它自己 单独 | 更新日期: 2023-09-27 17:56:04

假设我的 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 服务,如果我真的要获得并行行为,我可能应该打开多个连接。如果我只使用一个连接,我的直觉告诉我,即使我有多个线程,我也会得到串行行为。

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

单独的线程是否需要它自己的版本控制服务器实例才能与另一个实例同时工作

看起来使用 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;
        }