如何使用回调转换为异步方法

时间:2014-02-19 08:34:07

标签: c# multithreading asynchronous threadpool

我有以下情况。我正在使用.net 4.5.1

我有一个名为ServiceRequest();

的班级

请求使用tcpclient与服务器建立连接并从中获取数据。

运行ServiceRequest.Start()时启动计时器,然后每隔x分钟向服务器发出一次请求。 (我目前在服务类中没有线程)

所以我现在使用它的方式:

private List<ServiceRequest> ServiceCommuncators = new List<ServiceRequest>();

//Load Services To monitor
serviceResp = new EFServiceRepository();
IList<Service> Services = GetAllServices();

//Setup Services
ItterateServices(Services);

//Start Service Requests
StartServiceRequests();

//Functions:
private void ItterateServices(IList<Service> services)
    {
        for (int i = 0; i < services.Count; i++)
        {
            ServiceCommuncators.Add(new ServiceRequest(i, services[i].IpAddress, services[i].Port, services[i].PollInterval, ServicRequestCallBackFunction));
        }
    }

private void StartServiceRequests()
{
  foreach (ServiceRequest s in ServiceCommuncators)
  {
     s.Start();
  }
}

  public void ServicRequestCallBackFunction(ResponseObject response)
    {
        if (response.Successfull)
        {
            RecievedsuccessfulResponse(response);
        }
        else
        {
            // ServiceRequest Failed
            RecievedFailedResponse(response);
        }
    }

现在我想让每个请求以异步方式运行。

  1. 这样做最好的方法是什么?
    我是否只为每个请求创建一个线程?或者我应该使用线程池?我是线程的新手,所以请简单地尝试解释,也只是注意不会超过50个ServiceRequests同时运行。我不确定这是否可以在实现它的方式中发挥作用。

  2. 我关注的是回调函数,现在一旦我对每个RequestService进行了线程化,我是否需要对回调函数做任何事情,因为它会在任何给定时间从几个不同的线程调用? / p>

2 个答案:

答案 0 :(得分:1)

您有4个选项

  1. 使用System.Threading's Thread
    这可能是最糟糕的选择,因为它没有利用 ThreadPool 。而且由于你正在调整大量的线程(每个线程有大约1Mb的开销,因为每个线程都有其他东西)
  2. 使用ThreadPoll.QueueUserWorkItem 这样更好,因为它利用了 ThreadPool 。但是它没有其他方法灵活,因为它不允许你从回调中返回任何内容
  3. 使用Task.Factory.StartNew
    如果由于某种原因你不能使用async / await,这就是你要走的路。它利用了 ThreadPool ,并且在使用方式方面非常灵活。 StartNew<T>返回Task<T>您可以保留的结果,当您需要结果时,您可以调用'task.Result'来阻止调用线程直到任务完成。
  4. 使用async/await
    这是现在最简单,最流行的异步方式,这是正确的。它只是(3)的语法糖。
  5. 要使用async / await,您的方法必须返回Task<T>,在您的情况下Task<ResponseObject>。按照惯例,方法名称以Async结尾,并具有关键字async

    在您的示例中,您可以更改ServiceRequest的Start方法,使其为异步:

    public async Task<ResponseObject> StartAsync()
    {
      //make the request using await
      //for example return await caller.PerformRequestAsync
      //an important note here, if the method you need to call is not async, you can use await Task.Factory.StartNew<ResponseObject>(method that returns ResponseObject)
    
      return response; //response is an instance of ResponseObject
    }
    

    然后您可以更改StartServiceRequests并确保定期调用它(而不是在启动时执行[这只是一个建议]):

    private async void StartServiceRequestsAsync()
    {
      IEnumerable<Task<ResponseObject>> requests = new List<Task<ResponseObject>>();
      foreach (ServiceRequest s in ServiceCommuncators)
      {
         requests.Add(s.StartAsync()); //this is non-blocking
      }
    
      foreach(var executingRequest in requests)
      {
         ResponseObject response = await executingRequest;
         //do something with response
      }
    }
    

    这将利用 ThreadPool ,它将管理当前正在执行的线程数,并将管理现有线程的创建和处理。有时,了解如何将新线程添加到 ThreadPool 非常重要。使用Task.Factory.StartNew(或使用async / await)时,不一定会立即创建新线程。

    有一个.5秒的等待,检查 ThreadPool 是否没有改变(意味着所有线程都在工作),然后添加新线程(再次,添加一个线程有很大的成本)。

    但在你的情况下,你想要执行50个并行请求(你知道每个请求需要时间,即超过.5秒),所以你想要所有人开始。您可以更改 ThreadPool SetMinThreads)中的最小线程数并将其设置为50(这并不意味着将立即在 ThreadPool中创建50个线程< / em>,它只意味着.5秒没有等待,直到ThreadPool达到50个线程)。

    以下是一个例子:

    int minWorker, minIOC;
    // Get the current settings.
    ThreadPool.GetMinThreads(out minWorker, out minIOC);
    // Change the minimum number of worker threads to 50, but 
    // keep the old setting for minimum asynchronous I/O  
    // completion threads. 
    if (ThreadPool.SetMinThreads(50, minIOC))
    {
        // The minimum number of threads was set successfully.
    }
    else
    {
        // The minimum number of threads was not changed.
    }
    

答案 1 :(得分:0)

首先,您绝对不希望为此手动创建线程。所以是的,你应该使用线程池。根据您所定位的.net框架的版本,您可以使用TPL(任务并行库)创建任务以进行服务调用。这些任务使用线程池中的线程执行代码(默认情况下)。可以找到更多信息here

如果使用TPL而不是回调,则可以使用continuation,并在代码执行后运行其他任务。