65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 远程处理:长时间调用

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (7投票s)

2003年10月8日

3分钟阅读

viewsIcon

66022

downloadIcon

1203

有时服务器需要执行耗时的计算来响应客户端的请求。 如果此类调用很少见或不需要太多时间,则不是问题。 如果您的解决方案显示大约有 20-30 个此类调用同时执行的趋势,则您应该阅读本文

引言

有时服务器需要执行耗时的计算来响应客户端的请求。 如果此类调用很少见或不需要太多时间,则不是问题。 如果您的解决方案显示大约有 20-30 个此类调用同时执行的趋势,则您应该考虑以下事项。 任何进行远程调用的进程都应该被计算在内,因为执行时间通常是明显的,并且它们不应该占用从 ThreadPool 中获取的线程。

  1. 您的服务器可能会遇到 ThreadPool 的限制。 在这种情况下,所有 .NET 远程处理内容以及依赖于此的所有内容都将停止工作。
  2. 您应该在客户端应用合理的超时时间。
  3. 不要忘记赞助。 客户端应该持有服务器业务对象,并且服务器可能希望将赞助者附加到客户端的对象。 实际上,这些事情取决于设计,因此我们不会在本文中考虑它们。
  4. 进度条通常根本不是问题。 有关示例,请参见本文

通常,我们的计划看起来是这样的:我们发起一个动作,将我们的请求发送到服务器。 服务器接收它并在专用的后台线程中运行一个耗时的进程,以避免和 ThreadPool 相关的问题。 我们需要从该线程接收结果,因此我们提供回调来接收结果。

使用代码(第一个解决方案)

让我们研究第一个示例。 已知层仅包含接口。 以下是如何使用文章或代码的简要说明 - 类名、方法和属性、任何技巧或提示。

public interface IReceiveLengthyOperationResult
{
  void ReceiveResult(string operationResult);
} 

public interface IInitiateLengthyOperation
{
  void StartOperation(string parameter, 
    IReceiveLengthyOperationResult resultReceiver);
}

客户端发起操作执行,并使用 ManualResetEvent 类的实例来检查超时。

static void Main(string[] args)
{
   // Initiates the operation
   iInitiateLengthyOperation.StartOperation("Operation N" + 
         random.Next(100).ToString(), new Client());     

   // and wait until it finishes or time is up
   if (! InvocationCompletedEvent.WaitOne(5000, false))
     Console.WriteLine("Time is up.");
}   

// Is set when the invocation is completed.
public static ManualResetEvent InvocationCompletedEvent = 
          new ManualResetEvent(false);   

// Is called by an operation executer to transfer the operation 
// result to the client.
public void ReceiveResult(string operationResult)
{
   Console.WriteLine("A result has been received from the server:
     {0}.", operationResult);
   InvocationCompletedEvent.Set();
}

服务器实现长时间操作提供程序,该提供程序创建在单独线程中运行的业务对象。

public class LengthyOperationImplementation
{
  // Starts a lengthy process in the separate background thread.
  public LengthyOperationImplementation(string parameter,
         IReceiveLengthyOperationResult resultReceiver)
  {
    this._parameter = parameter;
    this._iReceiveLengthyOperationResult = resultReceiver;

    Thread thread = new Thread(new ThreadStart(this.Process));
    thread.IsBackground = true;
    thread.Start();
  }

  private string _parameter;
  private IReceiveLengthyOperationResult 
                               _iReceiveLengthyOperationResult;

  // Simulates a long-duration calculations.
  public void Process()
  {
    Console.WriteLine("We started long-duration calculations that 
        are expected to be finished in {0} milliseconds.",
        CALL_DURATION_MS);   
    Thread.Sleep(CALL_DURATION_MS);   
    Console.WriteLine("Calculations are finished. Calling client
           callback...");  

    // Warning: this call should be asyncrhonous. I can be 
    // lazy here only because I use Genuine Channels!
    this._iReceiveLengthyOperationResult.ReceiveResult
                        (this._parameter.ToUpper());
 }
}

通过这种方法,我们最终得到了什么?

  • 我们已经在单独的线程中运行了请求,并避免了任何与线程池相关的问题。 这很好,我们可以在那里进行任何耗时的处理或调用远程对等方的对象。
  • 对于每种类型的长时间调用,我们都必须编写两个接口。
  • 一般来说,我们需要编写一个单独的类并为每个请求创建它的实例。
  • 客户端能够跟踪超时。 我们可以使用 ThreadPool.RegisterWaitForSingleObject 方法来避免在客户端占用线程。

您是否认为花时间编写和支持每个此类调用的两个接口和一个类是个好主意? 没有? 我也不这么认为。 我们可以通过编写几行代码来获得相同的结果,而无需任何冗余接口或回调。

使用代码(第二个解决方案)

现在让我们使用 Genuine Channels 做同样的事情。 并考虑差异!

已知层仅包含一个接口,其中包含操作的声明。 它接收必要的参数并返回结果。

// Summary description for IPerformLengthyOperation.
public interface IPerformLengthyOperation
{
   // Performs a lengthy operation.
   string PerformOperation(string parameter);
}

服务器只是实现该操作并返回结果。 请注意,您拥有可以拥有的最简单的代码。 您只需执行一个操作并返回结果。

/// Starts a lengthy operation.
public string PerformOperation(string parameter)
{
  Console.WriteLine("We started long-duration calculations that are
     expected to be finished in {0} milliseconds.", CALL_DURATION_MS);
  Thread.Sleep(CALL_DURATION_MS);
  Console.WriteLine("Calculations are finished. Returning a
     result...");    

  return parameter.ToUpper();
}

客户端方面尽可能简单。 秘密在于安全会话参数,该参数强制线程上下文和此调用的自定义超时。

using(new SecurityContextKeeper(GlobalKeyStore. 
             DefaultCompressionLessContext + "?Thread&Timeout=5000"))
   iPerformLengthyOperation.PerformOperation("Operation N" + 
             random.Next(100).ToString());

如果您不想占用客户端的线程,请在同一安全会话参数中进行异步调用。 线程和超时参数也适用于它。

© . All rights reserved.