创建异步Web服务

  为了改善调用阻碍线程的长期运行的方法的XML Web服务方法的性能,你应该考虑把它们作为异步的XML Web服务方法发布。实现一个异步XML Web服务方法允许线程在返回线程池的时候执行其他的代码。这允许增加一个线程池中的有限数目的线程,这样提高了整体性能和系统的可伸缩性。

  通常,调用执行输入/输出操作的方法的XML Web服务方法适于作为异步实现。这样的方法的例子包括和其他的XML Web服务通讯、访问远程数据库、执行网络输入/输出和读写大文件方法。这些方法都花费大量时间执行硬件级操作,而把线程留着用来执行XML Web服务方法程序块。如果XML Web服务方法异步实现,那么线程可以被释放来执行其他的代码。

  不管一个XML Web服务方法是否异步实现,客户端都可以与之异步通讯。使用Web服务描述语言工具(WSDL.EXE)生成的.NET客户端中的代理类来实现异步通信,即使XML Web服务方法是同步实现。代理类包含用于与每个XML Web服务方法异步通信的Begin和End方法。因此,决定一个XML Web服务方法到底是异步还是同步要取决于性能。

  注意:实现一个异步的XML Web服务方法对客户端和服务器上的XML Web服务之间的HTTP连接没有影响。HTTP连接既不不会关闭也不用连接池化。

  实现一个异步的XML Web服务方法

  实现一个异步的XML Web服务方法遵循NET Framework异步设计模式

  把一个同步的XML Web服务方法分解为两个方法;其中每个都带有相同的基名--一个带有以Begin开头的名称,另一个带有以End开头的名称。

  Begin方法的参数表包含方法的功能中的所有的in和by引用参数。

  By引用参数是作为输入参数列出的。

  倒数第二个参数必须是AsyncCallback。AsyncCallback参数允许客户端提供一个委托,在方法调用完成的时候调用。当一个异步XML Web服务方法调用另一个异步方法,这个参数可以被传入那个方法的倒数第二个参数。最后一个参数是一个对象。对象参数允许一个调用者提供状态信息给方法。当一个异步XML Web服务方法调用另一个异步方法,这个参数可以被传入那个方法的最后一个参数。

  返回值必须是IAsyncResult类型的。

  下面的代码示例是一个Begin方法,有一个方法函数特定的String参数。

[WebMethod]
public IAsyncResult BeginGetAuthorRoyalties(String Author,
AsyncCallback callback, object asyncState)

  End方法的参数表由一个IAsyncResult类型的out和by引用参数组成。

  返回值与一个同步的XML Web服务方法的返回值类型相同。

  By引用参数是作为输出参数列出的。

  下面的代码示例是一个End方法,返回一个AuthorRoyalties用户定义的模式。

[WebMethod]
public AuthorRoyalties EndGetAuthorRoyalties(IAsyncResult
asyncResult)

  下面的代码示例是一个和另一个XML Web服务方法异步通讯的异步XML Web服务方法。

None.gif using  System;
None.gif
using  System.Web.Services; 
None.gif[WebService(Namespace
= " http://www.contoso.com/ " )]
ExpandedBlockStart.gifContractedBlock.gif
public   class  MyService : WebService  dot.gif {
InBlock.gif 
public RemoteService remoteService;
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public MyService() dot.gif{
InBlock.gif  
// Create a new instance of proxy class for 
InBlock.gif  
// the XML Web service to be called.
InBlock.gif
  remoteService = new RemoteService();
ExpandedSubBlockEnd.gif }

InBlock.gif 
// Define the Begin method.
InBlock.gif
  [WebMethod]
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public IAsyncResult BeginGetAuthorRoyalties(String Author,AsyncCallback callback, object asyncState) dot.gif{
InBlock.gif  
// Begin asynchronous communictation with a different XML Web
InBlock.gif  
// service.
InBlock.gif
  return remoteService.BeginReturnedStronglyTypedDS(Author,
InBlock.gif  callback,asyncState);
ExpandedSubBlockEnd.gif }

InBlock.gif 
// Define the End method.
InBlock.gif
 [WebMethod]
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public AuthorRoyalties EndGetAuthorRoyalties(IAsyncResultasyncResult) dot.gif{
InBlock.gif  
// Return the asynchronous result from the other XML Web service.
InBlock.gif
  return remoteService.EndReturnedStronglyTypedDS(asyncResult);
ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

None.gif using  System.Web.Services;
None.gif
using  System.Data;
None.gif
using  System;
None.gif
//  This imports the proxy class for the XML Web services
None.gif
//  that the sample communicates with.
None.gif
using  AsyncWS.localhost; 
None.gif
None.gif
namespace  AsyncWS
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif [WebService(Namespace
="http://www.contoso.com/")]
InBlock.gif 
public class MyService : System.Web.Services.WebService
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
public RemoteService remoteService;
InBlock.gif  
public MyService()
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   remo teService 
= new RemoteService();
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif [WebMethod]
InBlock.gif 
public IAsyncResult BeginGetAuthorRoyalties(String Author,
InBlock.gif AsyncCallback callback, Object asyncState) 
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
// Saves the current state for the call that gets the author's
InBlock.gif  
// royalties.
InBlock.gif
  AsyncStateChain state = new AsyncStateChain();
InBlock.gif  state.originalState 
= asyncState;
InBlock.gif  state.Author 
= Author;
InBlock.gif  state.originalCallback 
= callback;
InBlock.gif
InBlock.gif  
// Creates an intermediary callback.
InBlock.gif
  AsyncCallback chainedCallback = new
InBlock.gif  AsyncCallback(AuthorRoyaltiesCallback);
InBlock.gif  
return remoteService.BeginGetAuthors(chainedCallback,state);
ExpandedSubBlockEnd.gif }

InBlock.gif 
// Intermediate method to handle chaining the 
InBlock.gif 
// asynchronous calls.
InBlock.gif
 public void AuthorRoyaltiesCallback(IAsyncResult ar)
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  AsyncStateChain state 
= (AsyncStateChain)ar.AsyncState;
InBlock.gif  RemoteService rs 
= new RemoteService();
InBlock.gif
InBlock.gif  
// Gets the result from the call to GetAuthors.
InBlock.gif
  Authors allAuthors = rs.EndGetAuthors(ar);
InBlock.gif
InBlock.gif  Boolean found 
= false;
InBlock.gif  
// Verifies that the requested author is valid.
InBlock.gif
  int i = 0;
InBlock.gif  DataRow row;
InBlock.gif  
while (i < allAuthors.authors.Rows.Count && !found)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   row 
= allAuthors.authors.Rows[i];
InBlock.gif   
if (row["au_lname"].ToString() == state.Author) 
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    found 
= true;
ExpandedSubBlockEnd.gif   }

InBlock.gif   i
++;
ExpandedSubBlockEnd.gif  }

InBlock.gif  
if (found)
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   AsyncCallback cb 
= state.originalCallback;
InBlock.gif   
// Calls the second XML Web service, because the author is
InBlock.gif   
// valid.
InBlock.gif
   rs.BeginReturnedStronglyTypedDS(state.Author,cb,state);
ExpandedSubBlockEnd.gif  }

InBlock.gif  
else
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
// Cannot throw the exception in this function or the XML Web
InBlock.gif   
// service will hang. So, set the state argument to the
InBlock.gif   
// exception and let the End method of the chained XML Web
InBlock.gif   
// service check for it. 
InBlock.gif
   ArgumentException ex = new ArgumentException(
InBlock.gif    
"Author does not exist.","Author");
InBlock.gif   AsyncCallback cb 
= state.originalCallback;
InBlock.gif   
// Call the second XML Web service, setting the state to an
InBlock.gif   
// exception.
InBlock.gif
   rs.BeginReturnedStronglyTypedDS(state.Author,cb,ex);
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

InBlock.gif
InBlock.gif [WebMethod]
InBlock.gif 
public AuthorRoyalties EndGetAuthorRoyalties(IAsyncResult asyncResult) 
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
// Check whehter the first XML Web service threw an exception.
InBlock.gif
  if (asyncResult.AsyncState is ArgumentException)
InBlock.gif   
throw (ArgumentException) asyncResult.AsyncState;
InBlock.gif  
else
InBlock.gif   
return remoteService.EndReturnedStronglyTypedDS(asyncResult);
ExpandedSubBlockEnd.gif }

ExpandedSubBlockEnd.gif}

InBlock.gif
// Class to wrap the callback and state for the intermediate
InBlock.gif
// asynchronous operation.
InBlock.gif
public class AsyncStateChain 
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif 
public AsyncCallback originalCallback;
InBlock.gif 
public Object originalState;
InBlock.gif 
public String Author;
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

  和XML Web服务异步地通讯

  和一个XML Web服务异步通讯遵循被Microsoft.NET Framework其它部分使用的异步设计模式。然而,在你取得那些细节之前,重要的是注意一个XML Web服务不必特意的写来处理用于异步调用的异步请求。你使用Wsdl.exe为你的客户端创建的代理类自动地创建用于异步调用XML Web服务方法的方法。即使只有一个XML Web服务方法的同步实现也是这样的。

  .NET Framework异步方法调用设计模式

  用于调用异步方法的设计模式,尤其是用于.NET Framework,针对每个同步方法分别有两个异步方法。对每个同步方法,都有一个Begin异步方法和一个End异步方法。Begin方法被客户端调用来开始方法调用。也就是说,客户端指示这个方法来开始处理方法调用,但是立即返回。End方法被客户端调用来取得XML Web服务方法调用执行的处理结果。

  一个客户端如何知道何时调用End方法?.NET Framework定义了两种方法来实现客户端判断其时间。第一种是传送一个回调函数到Begin方法,当方法已经完成处理的时候调用。第二个方法是使用WaitHandle类的一个方法来导致客户端等待方法完成。当一个客户端实现第二个方法,并且调用Begin方法,返回值不是XML Web服务方法指定的数据类型,而是一个实现IAsyncResult接口的类型。IAsyncResult接口包含一个WaitHandle类型的AsyncWaitHandle属性,实现支持等待同步对象变为带有WaitHandle.WaitOne、WaitAny和WaitAll标记的方法。当一个同步对象被标记的时候,它指示等待特定的资源的线程可以访问资源的。如果一个XML Web服务客户端使用wait方法仅仅异步地调用一个XML Web服务方法,那么它可以调用WaitOne来等待XML Web服务方法完成处理。

  重要的是注意不管客户端选择来与XML Web服务异步通讯的两种方法中的哪一种,SOAP消息发送和接收都与同步通信时吻合。也就是说,只有一个SOAP请求和SOAP响应通过网络发送和接收。代理类通过使用一个不同的线程而不是客户端用来调用Begin方法的线程来处理SOAP响应。因此,客户端可以继续执行线程上的其它的工作,而代理类处理接收和操作SOAP响应。

  实现一个产生异步的方法调用的XML Web服务客户端

  用于从使用ASP.NET创建的XML Web服务客户端产生一个到XML Web服务的异步调用的体系结构被嵌入.NET Framework和由Wsdl.exe构造的代理类中。用于异步调用的设计模式被.NET Framework定义,代理类提供和一个XML Web服务异步通信的机制。当一个用于XML Web服务的代理类被使用Wsdl.exe构造的时候,有三个方法分别被创建,用于XML Web服务中的公共XML Web服务方法。下面的表格描述那三个方法。

  代理类中的方法名 描述

  <NameOfWebServiceMethod> 同步发送用于名为<NameOfWebServiceMethod>的XML Web服务方法的消息。

  Begin<NameOfWebServiceMethod> 开始与名为<NameOfWebServiceMethod>的XML Web服务方法的异步消息通信。

  End<NameOfWebServiceMethod> 结束与名为<NameOfWebServiceMethod>的XML Web服务方法的异步消息通信,从XML Web服务方法中取得完成的消息。

  下面的代码示例是一个XML Web服务方法,它可能花费相对长的时间来完成处理。因此,当你应该设置你的XML Web服务客户端来异步地调用XML Web服务方法的时候,它是一个很好的示例。

None.gif <% @ WebService Language = " C# "  Class = " PrimeFactorizer "   %>
None.gif
None.gif
using  System;
None.gif
using  System.Collections;
None.gif
using  System.Web.Services;
None.gif
ExpandedBlockStart.gifContractedBlock.gif
class  PrimeFactorizer  dot.gif {
InBlock.gif
InBlock.gif [WebMethod]
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public long[] Factorize(long factorizableNum)dot.gif{
InBlock.gif  ArrayList outList 
= new ArrayList();
InBlock.gif  
long i = 0;
InBlock.gif  
int j;
ExpandedSubBlockStart.gifContractedSubBlock.gif  
trydot.gif{
InBlock.gif   
long Check = factorizableNum;
InBlock.gif
InBlock.gif   
//Go through every possible integer
InBlock.gif   
//factor between 2 and factorizableNum / 2.
InBlock.gif   
//Thus, for 21, check between 2 and 10.
ExpandedSubBlockStart.gifContractedSubBlock.gif
   for (i = 2; i < (factorizableNum / 2); i++)dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif    
while(Check % i == 0)dot.gif{
InBlock.gif     outList.Add(i);
InBlock.gif     Check 
= (Check/i);
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif   }

InBlock.gif   
//Double-check to see how many prime factors have been added.
InBlock.gif   
//If none, add 1 and the number.
InBlock.gif
   j = outList.Count;
ExpandedSubBlockStart.gifContractedSubBlock.gif   
if (j == 0dot.gif{
InBlock.gif    outList.Add(
1);
InBlock.gif    outList.Add(factorizableNum);
ExpandedSubBlockEnd.gif   }

InBlock.gif   j 
= outList.Count;
InBlock.gif
InBlock.gif   
//Return the results and
InBlock.gif   
//create an array to hold them.
InBlock.gif
   long[] primeFactor = new long[j];
ExpandedSubBlockStart.gifContractedSubBlock.gif   
for (j = 0; j < outList.Count; j++)dot.gif{
InBlock.gif    
//Pass the values one by one, making sure
InBlock.gif    
//to convert them to type ulong.
InBlock.gif
    primeFactor[j] = Convert.ToInt64(outList[j]);
ExpandedSubBlockEnd.gif   }

InBlock.gif   
return primeFactor;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockStart.gifContractedSubBlock.gif  
catch (Exception) dot.gif{
InBlock.gif   
return null;
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}
 

  下面的代码示例是一个Wsdl.exe生成的代理类的一部分,用于上述XML Web服务方法。注意BeginFactorize和EndFactorize方法,因为它们被用来与Factorize XML Web服务方法异步通信。

ExpandedBlockStart.gif ContractedBlock.gif public   class  PrimeFactorizer : System.Web.Services.Protocols.SoapHttpClientProtocol  dot.gif {
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public long[] Factorize(long factorizableNum) dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif  
object[] results = this.Invoke("Factorize"new object[] dot.gif{ factorizableNum});
InBlock.gif   
return ((long[])(results[0]));
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public System.IAsyncResult BeginFactorize(long factorizableNum, System.AsyncCallback callback, object  asyncState) dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif  
return this.BeginInvoke("Factorize"new object[] dot.gif{
ExpandedSubBlockEnd.gif  factorizableNum}
, callback, asyncState);
ExpandedSubBlockEnd.gif }

InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public long[] EndFactorize(System.IAsyncResult asyncResult) dot.gif{
InBlock.gif  
object[] results = this.EndInvoke(asyncResult);
InBlock.gif  
return ((long[])(results[0]));
ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

  有两个方法用来和XML Web服务方法异步通信。下面的代码示例说明了如何与一个XML Web服务方法异步通信,并且使用回调函数来取得XML Web服务方法的结果。

None.gif using  System;
None.gif
using  System.Runtime.Remoting.Messaging;
None.gif
using  MyFactorize;
None.gif
None.gif
class  TestCallback
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public static void Main()dot.gif{
InBlock.gif  
long factorizableNum = 12345;
InBlock.gif  PrimeFactorizer pf 
= new PrimeFactorizer();
InBlock.gif
InBlock.gif  
//Instantiate an AsyncCallback delegate to use as a parameter
InBlock.gif  
//in the BeginFactorize method.
InBlock.gif
  AsyncCallback cb = new AsyncCallback(TestCallback.FactorizeCallback);
InBlock.gif
InBlock.gif  
// Begin the Async call to Factorize, passing in our
InBlock.gif  
// AsyncCalback delegate and a reference
InBlock.gif  
// to our instance of PrimeFactorizer.
InBlock.gif
  IAsyncResult ar = pf.BeginFactorize(factorizableNum, cb, pf);
InBlock.gif
InBlock.gif  
// Keep track of the time it takes to complete the async call
InBlock.gif  
// as the call proceeds.
InBlock.gif
  int start = DateTime.Now.Second;
InBlock.gif  
int currentSecond = start;
ExpandedSubBlockStart.gifContractedSubBlock.gif  
while (ar.IsCompleted == false)dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif   
if (currentSecond < DateTime.Now.Second) dot.gif{
InBlock.gif    currentSecond 
= DateTime.Now.Second;
InBlock.gif    Console.WriteLine(
"Seconds Elapseddot.gif" + (currentSecond - start).ToString() );
ExpandedSubBlockEnd.gif   }

ExpandedSubBlockEnd.gif  }

InBlock.gif  
// Once the call has completed, you need a method to ensure the
InBlock.gif  
// thread executing this Main function 
InBlock.gif  
// doesn't complete prior to the call-back function completing.
InBlock.gif
  Console.Write("Press Enter to quit");
InBlock.gif  
int quitchar = Console.Read();
ExpandedSubBlockEnd.gif }

InBlock.gif 
// Set up a call-back function that is invoked by the proxy class
InBlock.gif 
// when the asynchronous operation completes.
InBlock.gif
 public static void FactorizeCallback(IAsyncResult ar)
ExpandedSubBlockStart.gifContractedSubBlock.gif 
dot.gif{
InBlock.gif  
// You passed in our instance of PrimeFactorizer in the third
InBlock.gif  
// parameter to BeginFactorize, which is accessible in the
InBlock.gif  
// AsyncState property.
InBlock.gif
  PrimeFactorizer pf = (PrimeFactorizer) ar.AsyncState;
InBlock.gif  
long[] results;
InBlock.gif
InBlock.gif  
// Get the completed results.
InBlock.gif
  results = pf.EndFactorize(ar);
InBlock.gif
InBlock.gif  
//Output the results.
InBlock.gif
  Console.Write("12345 factors into: ");
InBlock.gif  
int j;
ExpandedSubBlockStart.gifContractedSubBlock.gif  
for (j = 0; j<results.Length;j++)dot.gif{
InBlock.gif   
if (j == results.Length - 1)
InBlock.gif    Console.WriteLine(results[j]);
InBlock.gif   
else 
InBlock.gif    Console.Write(results[j] 
+ "");
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

  下面的代码示例说明了如何与一个XML Web服务方法异步通信,然后使用一个同步对象来等待处理结束。

None.gif //  Async Variation 2.
None.gif
//  Asynchronously invoke the Factorize method, 
None.gif
// without specifying a call back.
None.gif
using  System;
None.gif
using  System.Runtime.Remoting.Messaging;
None.gif
//  MyFactorize, is the name of the namespace in which the proxy class is
None.gif
//  a member of for this sample.
None.gif
using  MyFactorize; 
None.gif
None.gif
class  TestCallback
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif 
public static void Main()dot.gif{
InBlock.gif  
long factorizableNum = 12345;
InBlock.gif  PrimeFactorizer pf 
= new PrimeFactorizer();
InBlock.gif
InBlock.gif  
// Begin the Async call to Factorize.
InBlock.gif
  IAsyncResult ar = pf.BeginFactorize(factorizableNum, nullnull);
InBlock.gif
InBlock.gif  
// Wait for the asynchronous operation to complete.
InBlock.gif
  ar.AsyncWaitHandle.WaitOne();
InBlock.gif
InBlock.gif  
// Get the completed results.
InBlock.gif
  long[] results; 
InBlock.gif  results 
= pf.EndFactorize(ar);
InBlock.gif
InBlock.gif  
//Output the results.
InBlock.gif
  Console.Write("12345 factors into: ");
InBlock.gif  
int j;
ExpandedSubBlockStart.gifContractedSubBlock.gif  
for (j = 0; j<results.Length;j++)dot.gif{
InBlock.gif   
if (j == results.Length - 1)
InBlock.gif    Console.WriteLine(results[j]);
InBlock.gif   
else 
InBlock.gif    Console.Write(results[j] 
+ "");
ExpandedSubBlockEnd.gif  }

ExpandedSubBlockEnd.gif }

ExpandedBlockEnd.gif}

  注意:如果FactorizeCallback是一个需要同步化/线成亲和上下文的上下文绑定类,那么回调被通过上下文分配体系结构来分配。换句话说,相对于它的对这样的上下文的调用者,回调可能异步的执行。在方法标记上有单向修饰词的精确的语义。这指的是任何这样的方法调用可能同步地或异步地执行,相对于调用者,并且在执行控制返回给它的时候,调用者不能产生任何关于完成这样一个调用的假设。

  而且,在异步操作完成之前调用EndInvoke将阻塞调用者。使用相同的AsyncResult再次调用它的行为是不确定的。

  Cancel方法是一个在过去一段特定时间之后取消方法处理的请求。注意它是一个客户端的请求,并且最好服务器对此有所承诺。在接收到方法已经被取消的消息之后,客户端就不必做服务器是否已经停止处理的假设了。客户端最好不要破坏资源,例如文件对象,因为服务器可能仍然需要使用它们。IAsyncResult实例的IsCompleted属性在服务器结束它的处理之后将被设置为true,不再使用任何客户端提供的资源。因此,IsCompleted属性设置为true之后,客户端就可以安全的销毁资源了。

文章来源:http://www.xxju.net/article/200509/22_1840333525.htm

转载于:https://www.cnblogs.com/pyw0818/archive/2006/03/19/353479.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值