Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/305.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 将阻塞调用包装为异步,以便更好地重用线程和响应UI_C#_.net_Multithreading_Task Parallel Library_Async Await - Fatal编程技术网

C# 将阻塞调用包装为异步,以便更好地重用线程和响应UI

C# 将阻塞调用包装为异步,以便更好地重用线程和响应UI,c#,.net,multithreading,task-parallel-library,async-await,C#,.net,Multithreading,Task Parallel Library,Async Await,我有一个类,负责通过调用遗留类来检索产品可用性。这个遗留类本身通过阻塞网络调用在内部收集产品数据。 请注意,我不能修改遗留API的代码。因为所有产品都是相互独立的,所以我想并行地收集信息,而不创建任何不必要的线程,也不阻塞调用这个遗留API时被阻塞的线程。有了这个背景,这里是我的基本课程 class Product { public int ID { get; set; } public int VendorID { get; set; }

我有一个类,负责通过调用遗留类来检索产品可用性。这个遗留类本身通过阻塞网络调用在内部收集产品数据。 请注意,我不能修改遗留API的代码。因为所有产品都是相互独立的,所以我想并行地收集信息,而不创建任何不必要的线程,也不阻塞调用这个遗留API时被阻塞的线程。有了这个背景,这里是我的基本课程

class Product
    {
        public int ID { get; set; }
        public int  VendorID { get; set; }
        public string Name { get; set; }
    }

    class ProductSearchResult
    {
        public int ID { get; set; }
        public int AvailableQuantity { get; set; }
        public DateTime ShipDate { get; set; }
        public bool Success { get; set; }
        public string Error { get; set; }
    }

class ProductProcessor
    {
        List<Product> products;
        private static readonly SemaphoreSlim mutex = new SemaphoreSlim(2);
        CancellationTokenSource cts = new CancellationTokenSource();
        public ProductProcessor()
        {
            products = new List<Product>()
            {
                new Product() { ID = 1, VendorID = 100, Name = "PC" },
                new Product() { ID = 2, VendorID = 101, Name = "Tablet" },
                new Product() { ID = 3, VendorID = 100, Name = "Laptop" },
                new Product() { ID = 4, VendorID = 102, Name = "GPS" },
                new Product() { ID = 5, VendorID = 107, Name = "Mars Rover" }
            };

        }

        public async void Start()
        {
            Task<ProductSearchResult>[] tasks = new Task<ProductSearchResult>[products.Count];
            Parallel.For(0, products.Count(), async i =>
            {
                tasks[i] = RetrieveProductAvailablity(products[i].ID, cts.Token);

            });



            Task<ProductSearchResult> results = await Task.WhenAny(tasks);

            // Logic for waiting on indiviaul tasks and reporting results

        }

        private async Task<ProductSearchResult> RetrieveProductAvailablity(int productId, CancellationToken cancellationToken)
        {
            ProductSearchResult result = new ProductSearchResult();
            result.ID = productId;

            if (cancellationToken.IsCancellationRequested)
            {
                result.Success = false;
                result.Error = "Cancelled.";
                return result;
            }

            try
            {
                await mutex.WaitAsync();
                if (cancellationToken.IsCancellationRequested)
                {
                    result.Success = false;
                    result.Error = "Cancelled.";
                    return result;
                }

                LegacyApp app = new LegacyApp();
                bool success = await Task.Run(() => app.RetrieveProductAvailability(productId));
                if (success)
                {
                    result.Success = success;
                    result.AvailableQuantity = app.AvailableQuantity;
                    result.ShipDate = app.ShipDate;
                }
                else
                {
                    result.Success = false;
                    result.Error = app.Error;
                }
            }
            finally
            {
                mutex.Release();
            }

            return result;

        }

    }
类产品
{
公共int ID{get;set;}
public int VendorID{get;set;}
公共字符串名称{get;set;}
}
类ProductSearchResult
{
公共int ID{get;set;}
public int availableequality{get;set;}
公共日期时间ShipDate{get;set;}
公共bool成功{get;set;}
公共字符串错误{get;set;}
}
类产品处理器
{
列出产品清单;
私有静态只读信号量lim mutex=新信号量lim(2);
CancellationTokenSource cts=新的CancellationTokenSource();
公共产品处理器()
{
产品=新列表()
{
新产品(){ID=1,VendorID=100,Name=“PC”},
新产品(){ID=2,VendorID=101,Name=“Tablet”},
新产品(){ID=3,VendorID=100,Name=“Laptop”},
新产品(){ID=4,VendorID=102,Name=“GPS”},
新产品(){ID=5,VendorID=107,Name=“Mars Rover”}
};
}
公共异步void Start()
{
Task[]tasks=新任务[products.Count];
Parallel.For(0,products.Count(),异步i=>
{
tasks[i]=retrieveProductAvailability(产品[i].ID,cts.Token);
});
任务结果=等待任务。wheny(任务);
//等待单个任务和报告结果的逻辑
}
专用异步任务检索ProductAvailability(int-productId,CancellationToken CancellationToken)
{
ProductSearchResult=新的ProductSearchResult();
result.ID=productId;
if(cancellationToken.IsCancellationRequested)
{
结果:成功=错误;
结果。错误=“已取消。”;
返回结果;
}
尝试
{
wait mutex.WaitAsync();
if(cancellationToken.IsCancellationRequested)
{
结果:成功=错误;
结果。错误=“已取消。”;
返回结果;
}
LegacyApp=新的LegacyApp();
bool success=wait Task.Run(()=>app.RetrieveProductAvailability(productId));
如果(成功)
{
结果:成功=成功;
result.availablequality=app.availablequality;
result.ShipDate=app.ShipDate;
}
其他的
{
结果:成功=错误;
result.Error=app.Error;
}
}
最后
{
mutex.Release();
}
返回结果;
}
}
考虑到我试图在同步API上封装异步,我有两个问题

  • 通过在Task.Run中使用Parallel.For和wrapping Legay API调用,我是否创建了任何不必要的线程,这些线程本可以避免,而不会阻止调用线程,因为我们将在UI中使用这些代码
  • 这段代码看起来仍然是线程安全的吗
  • 我是否创建了本可以避免的任何不必要的线程 不阻塞调用线程,因为我们将在UI中使用此代码

    对。您的代码通过
    Parallel.ForEach
    旋转新线程,然后在
    retrieveProductAvailability
    内部再次旋转新线程。没有这个必要

    async wait
    Parallel.ForEach
    ,因为它将异步lambda转换为
    异步void
    方法,而不是
    异步任务

    我建议您放弃Parallel.ForEach和wrapped sync调用,然后执行以下操作:

    将方法调用从异步更改为同步(因为它实际上根本不是异步的):

    与此相反:

    bool success = await Task.Run(() => app.RetrieveProductAvailability(productId));
    
    同步调用方法调用:

    bool success = app.RetrieveProductAvailability(productId));
    
    然后显式调用
    任务。对所有任务运行

    var productTasks = products.Select(product => Task.Run(() => 
                                       RetrieveProductAvailablity(product.ID, cts.Token))
    
    await Task.WhenAll(productTasks);
    

    通常,不建议公开编译器将向您发出有关
    async
    lambda的警告。仔细阅读;它告诉你它不是异步的。在那里使用
    async
    没有意义。另外,不要使用
    async void

    因为您的底层API是阻塞的——并且没有办法改变这一点——异步代码不是一个选项。我建议使用多个
    任务。运行
    调用或
    并行。For
    ,但不能同时使用这两个。所以让我们使用并行。实际上,我们使用并行LINQ,因为您正在转换序列

    retrieveProductAvailability
    异步是没有意义的;除了节流之外,它只做阻塞工作,而并行方法具有更自然的节流支持。这使您的方法看起来像:

    private ProductSearchResult RetrieveProductAvailablity(int productId, CancellationToken cancellationToken)
    {
      ... // no mutex code
      LegacyApp app = new LegacyApp();
      bool success = app.RetrieveProductAvailability(productId);
      ... // no mutex code
    }
    
    然后,您可以执行如下并行处理:

    public void Start()
    {
      ProductSearchResult[] results = products.AsParallel().AsOrdered()
          .WithCancellation(cts.Token).WithDegreeOfParallelism(2)
          .Select(product => RetrieveProductAvailability(product.ID, cts.Token))
          .ToArray();
      // Logic for waiting on indiviaul tasks and reporting results
    }
    
    从UI线程,您可以使用
    任务调用该方法。运行

    async void MyUiEventHandler(...)
    {
      await Task.Run(() => processor.Start());
    }
    
    这将保持您的业务逻辑干净(仅同步/并行代码),将此工作移出UI线程(使用
    Task.Run
    )的责任属于UI层

    更新:我添加了对
    AsOrdered
    的调用,以确保结果数组的顺序与产品相同
    async void MyUiEventHandler(...)
    {
      await Task.Run(() => processor.Start());
    }
    
    public async Task Start()
    {
      var tasks = products.Select(product =>
          ProcessAvailabilityAsync(product.ID, cts.Token));
      await Task.WhenAll(tasks);
    }
    
    private SemaphoreSlim mutex = new SempahoreSlim(2);
    private async Task ProcessAvailabilityAsync(int id, CancellationToken token)
    {
      await mutex.WaitAsync();
      try
      {
        var result = await RetrieveProductAvailability(id, token);
        // Logic for reporting results
      }
      finally
      {
        mutex.Release();
      }
    }