## 34.4.异步命令处理 [](<>) 这个[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)该函数足以在正常的同步应用程序中提交命令。然而,它有一些对某些用户很重要的缺陷: - [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)等待命令完成。应用程序可能还有其他工作要做(比如维护用户界面),在这种情况下,它不想阻止等待响应。 - 由于客户端应用程序在等待结果时暂停执行,因此应用程序很难决定是否要尝试取消正在执行的命令。(这可以通过信号处理器完成,但不能通过其他方式完成。) - [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)只能返回一个`PGresult`结构如果提交的命令字符串包含多个SQL命令,则除最后一个命令外`PGresult`被丢弃[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC). - [`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)始终收集命令的整个结果,并将其缓冲在单个`PGresult`。虽然这简化了应用程序的错误处理逻辑,但对于包含多行的结果来说可能不切实际。 不喜欢这些限制的应用程序可以使用[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)它由以下部分构成:[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)和[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT).还有[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),[`PQsendPrepare`](libpq-async.html#LIBPQ-PQSENDPREPARE),[`PQsendQueryPrepared`](libpq-async.html#LIBPQ-PQSENDQUERYPREPARED),[`PQsendDescribePrepared`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPREPARED), 和[`PQsendDescribePortal`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPORTAL), 它可以与[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)复制的功能[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS),[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE),[`PQexec 准备`](libpq-exec.html#LIBPQ-PQEXECPREPARED),[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED), 和[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL)分别。 `PQsendQuery`[](<>) 向服务器提交命令而不等待结果。如果命令成功发送则返回 1,否则返回 0(在这种情况下,使用[`PQerrorMessage`](libpq-status.html#LIBPQ-PQERRORMESSAGE)以获取有关故障的更多信息)。 ``` int PQsendQuery(PGconn *conn, const char *command); ``` 调用成功后[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY), 称呼[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)一次或多次获得结果。[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)不能再次调用(在同一连接上),直到[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)返回了一个空指针,表示命令执行完毕。 在管道模式下,不允许包含多个 SQL 命令的命令字符串。 `PQsendQueryParams`[](<>) 向服务器提交命令和单独的参数,而不等待结果。 ``` int PQsendQueryParams(PGconn *conn, const char *command, int nParams, const Oid *paramTypes, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat); ``` 这相当于[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)除了查询参数可以与查询字符串分开指定。该函数的参数的处理方式与[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS).喜欢[`PQexec 参数`](libpq-exec.html#LIBPQ-PQEXECPARAMS),它只允许查询字符串中有一个命令。 `PQsendPrepare`[](<>) 发送请求以使用给定参数创建准备好的语句,而无需等待完成。 ``` int PQsendPrepare(PGconn *conn, const char *stmtName, const char *query, int nParams, const Oid *paramTypes); ``` 这是一个异步版本[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)判断服务器是否成功创建了prepared statement。该函数的参数的处理方式与[`PQprepare`](libpq-exec.html#LIBPQ-PQPREPARE). `PQsendQueryPrepared`[](<>) 发送请求以执行具有给定参数的准备好的语句,而无需等待结果。 ``` int PQsendQueryPrepared(PGconn *conn, const char *stmtName, int nParams, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat); ``` 这类似于[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),但要执行的命令是通过命名先前准备的语句来指定的,而不是给出查询字符串。该函数的参数的处理方式与[`PQexec 准备`](libpq-exec.html#LIBPQ-PQEXECPREPARED). `PQsendDescribePrepared`[](<>) 提交请求以获取有关指定预准备语句的信息,而无需等待完成。 ``` int PQsendDescribePrepared(PGconn *conn, const char *stmtName); ``` 这是一个异步版本[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)来获得结果。该函数的参数的处理方式与[`PQdescribePrepared`](libpq-exec.html#LIBPQ-PQDESCRIBEPREPARED). `PQsendDescribePortal`[](<>) 提交请求以获取有关指定门户的信息,而无需等待完成。 ``` int PQsendDescribePortal(PGconn *conn, const char *portalName); ``` 这是一个异步版本[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL):如果能够发送请求,则返回 1,否则返回 0。调用成功后,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)来获得结果。该函数的参数的处理方式与[`PQdescribePortal`](libpq-exec.html#LIBPQ-PQDESCRIBEPORTAL). `PQgetResult`[](<>) 等待前一个结果的下一个结果[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY),[`PQsendQueryParams`](libpq-async.html#LIBPQ-PQSENDQUERYPARAMS),[`PQsendPrepare`](libpq-async.html#LIBPQ-PQSENDPREPARE),[`PQsendQueryPrepared`](libpq-async.html#LIBPQ-PQSENDQUERYPREPARED),[`PQsendDescribePrepared`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPREPARED),[`PQsendDescribePortal`](libpq-async.html#LIBPQ-PQSENDDESCRIBEPORTAL), 或者[`PQpipelineSync`](libpq-pipeline-mode.html#LIBPQ-PQPIPELINESYNC)调用,并返回它。命令完成时返回一个空指针,不会再有结果。 ``` PGresult *PQgetResult(PGconn *conn); ``` [`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)必须重复调用,直到它返回一个空指针,表示命令完成。(如果在没有命令处于活动状态时调用,[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)将立即返回一个空指针。)每个非空结果来自[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)应该使用相同的处理`PG结果`前面描述的访问器函数。不要忘记释放每个结果对象[`PQclear`](libpq-exec.html#LIBPQ-PQCLEAR)完成后。注意[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)仅当命令处于活动状态且必要的响应数据尚未被读取时才会阻塞[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT). 在流水线模式下,`PQgetResult`除非发生错误,否则将正常返回;对于在导致错误的查询之后发送的任何后续查询,直到(并且不包括)下一个同步点,类型的特殊结果`PGRES_PIPELINE_ABORTED`将被返回,并在其后返回一个空指针。当达到管道同步点时,类型为`PGRES_PIPELINE_SYNC`将被退回。紧跟在同步点之后的下一个查询的结果(即同步点之后不返回空指针)。 ### 笔记 即使当[`PQresult状态`](libpq-exec.html#LIBPQ-PQRESULTSTATUS)表示致命错误,[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)应该调用直到它返回一个空指针,以允许 libpq 完全处理错误信息。 使用[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)和[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)解决其中之一[`执行程序`](libpq-exec.html#LIBPQ-PQEXEC)的问题:如果一个命令字符串包含多个SQL命令,则可以单独获取这些命令的结果。(顺便说一下,这允许一种简单形式的重叠处理:客户端可以处理一个命令的结果,而服务器仍在处理同一命令字符串中的后续查询。) 另一个经常需要的功能,可以通过[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)和[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)一次检索大查询结果一行。这在[第 34.6 节](libpq-single-row-mode.html). 本身,调用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)仍然会导致客户端阻塞,直到服务器完成下一个 SQL 命令。这可以通过正确使用另外两个功能来避免: `PQconsume输入`[](<>) 如果可以从服务器获得输入,则使用它。 ``` int PQconsumeInput(PGconn *conn); ``` [`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)通常返回 1 表示“没有错误”,但如果出现某种故障则返回 0(在这种情况下[`PQerrorMessage`](libpq-status.html#LIBPQ-PQERRORMESSAGE)可以咨询)。请注意,结果并未说明是否实际收集了任何输入数据。打电话后[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),应用程序可以检查[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)和/或`PQ 通知`看看他们的状态是否发生了变化。 [`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)即使应用程序还没有准备好处理结果或通知,也可以调用。该函数将读取可用数据并将其保存在缓冲区中,从而导致`选择()`阅读就绪指示离开。该应用程序因此可以使用[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)清除`选择()`立即状况,然后在闲暇时检查结果。 `PQis忙碌`[](<>) 如果命令忙,则返回 1,即[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)会阻塞等待输入。返回 0 表示[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)可以在保证不阻塞的情况下调用。 ``` int PQisBusy(PGconn *conn); ``` [`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)本身不会尝试从服务器读取数据;所以[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)必须先调用,否则忙碌状态永远不会结束。 使用这些函数的典型应用程序将有一个主循环,它使用`选择()`要么`轮询()`等待它必须响应的所有条件。条件之一将从服务器输入,根据`选择()`表示文件描述符上的可读数据[`PQsocket`](libpq-status.html#LIBPQ-PQSOCKET).当主循环检测到输入就绪时,它应该调用[`PQconsume输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT)读取输入。然后它可以调用[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY), 其次是[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)如果[`PQis忙碌`](libpq-async.html#LIBPQ-PQISBUSY)返回假 (0)。它也可以调用`PQ 通知`检测`通知`消息(见[第 34.9 节](libpq-notify.html))。 使用的客户端[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY)/[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)还可以尝试取消服务器仍在处理的命令;看[第 34.7 节](libpq-cancel.html).但不管[`取消`](libpq-cancel.html#LIBPQ-PQCANCEL),应用程序必须使用[`PQgetResult`](libpq-async.html#LIBPQ-PQGETRESULT)。成功取消只会导致命令提前终止。 通过使用上述功能,可以避免在等待来自数据库服务器的输入时发生阻塞。但是,应用程序仍有可能阻止等待向服务器发送输出。这种情况相对少见,但如果发送很长的SQL命令或数据值,就会发生这种情况。(如果应用程序通过`抄送`(然而)为了防止这种可能性并实现完全无阻塞的数据库操作,可以使用以下附加功能。 `PQsetnonblocking`[](<>) 设置连接的非阻塞状态。 ``` int PQsetnonblocking(PGconn *conn, int arg); ``` 如果需要,将连接状态设置为非阻塞*`阿格`*是1,如果*`阿格`*是0。如果正常,则返回0;如果错误,则返回1。 在非阻塞状态下,调用[`PQsendQuery`](libpq-async.html#LIBPQ-PQSENDQUERY),[`PQputline`](libpq-copy.html#LIBPQ-PQPUTLINE),[`PQputnbytes`](libpq-copy.html#LIBPQ-PQPUTNBYTES),[`PQputCopyData`](libpq-copy.html#LIBPQ-PQPUTCOPYDATA)和[`PQendcopy`](libpq-copy.html#LIBPQ-PQENDCOPY)如果需要再次调用,则不会阻止,而是返回错误。 注意[`PQexec`](libpq-exec.html#LIBPQ-PQEXEC)不支持非阻塞模式;如果它被调用,它将以阻塞方式运行。 `PQIS非阻塞`[](<>) 返回数据库连接的阻塞状态。 ``` int PQisnonblocking(const PGconn *conn); ``` 如果连接设置为非阻塞模式,则返回1;如果连接设置为阻塞模式,则返回0。 `PQflush`[](<>) 尝试将任何排队的输出数据刷新到服务器。如果成功(或如果发送队列为空),则返回0;如果由于某种原因失败,则返回-1;如果无法发送发送队列中的所有数据,则返回1(这种情况仅在连接未阻塞时发生)。 ``` int PQflush(PGconn *conn); ``` 在非阻塞连接上发送任何命令或数据后,调用[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH).如果返回1,则等待套接字变为读写就绪。如果已准备好写入,请致电[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)再一次如果它已准备就绪,请致电[`pqconsumer输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),然后打电话[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)再一次重复直到[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)返回0。(有必要检查read ready(读取准备就绪)并使用[`pqconsumer输入` ](libpq-async.html#LIBPQ-PQCONSUMEINPUT),因为服务器可以阻止向我们发送数据,例如通知消息,并且在我们读取数据之前不会读取我们的数据。)一旦[`PQflush`](libpq-async.html#LIBPQ-PQFLUSH)返回0,等待套接字读取就绪,然后如上所述读取响应。