The c-ares library is extremely popular and is in use by many projects. A few include libcurl, node.js, and Wireshark. It supports synchronous and asynchronous lookups of all record types. It also provides convenience functions which parse a few different record types (such as NAPTR) into easier to consume structures. Various options exist to tweak the behavior, such as forcing TCP, changing DNS server addresses, and retry behavior. Documentation is straight forward but does not include complete examples for different scenarios.
The documentation provided on the website is 2-3 years out of date. It also does not present an example or a suggested way of using the library. In order to come up with a working solution you have to experiment using the out of date documentation, using the source code as guidance, using other projects as guidance, and talking to other people who have used it.
Using the library is somewhat easy in the end though. It seems to want you to create a resolver "channel" for each query you want to do. While it is possible to use the same channel for multiple queries this is problematic as will be explained later. Once you have a channel you call functions telling it to resolve things and it does. Once resolved a callback is invoked with the result and a void pointer you pass in. The potential trouble, though, is that you need to synchronize with the thread processing the results if you want async with a long lived thread servicing the resolver channel. This is because unless a query is going on the c-ares library it will not provide you a file descriptor to poll on. If TCP is enabled the file descriptor for that is always returned. This is why the approach of creating an individual channel for each query and then spawning a thread (or using a threadpool) for the resolver channel for async seems to be the way to go. It removes the synchronization that has to occur when using the persistent long lived thread. In the case of sync resolution polling and processing in the calling thread is what you need to do, unless you otherwise wait in the calling thread and signal back from the persistent thread actually doing the processing. It's also not possible to cancel individual queries on a channel. Cancelling will cancel ALL queries on it. The c-ares library does not cache results at all.
The library is pretty much available everywhere.
Releases are made sporadically. Some years have more, some years have less. The mailing list has between 5 to 20 emails on average per month. Some posts go unanswered. Issues and contributions are submitted to the mailing list (while github pull requests can be done it is not preferred). Contributions are merged swiftly upon acceptance and vouching/testing by others. Going back through mailing list archives it's hard to determine the response time for reported issues. Many just get submitted with a patch.
The libunbound library is part of the unbound DNS resolver and shares much of the same code. It supports synchronous and asynchronous lookups of all record types as well as DNSSEC. Various options exist to tweak the behavior, such as forcing TCP, changing DNS server addresses, and retry behavior. Documentation is straight forward and includes various examples for scenarios. What is not provided is a convenience mechanism to parse record types into an easier to consume structure. The as-is DNS records are returned.
The documentation provided on the website (which is basically a man page) is up to date with the current implementation. Examples are provided for different use cases (synchronous, asynchronous) and they work. They are a suitable starting basis for experimenting with the library and can get you going fast.
The libunbound library uses what it call a "context" to refer to a resolver. A context can be used for multiple queries and each query are individually addressable. When you asynchronously do a query you get back an identifier which can be used to subsequently cancel that specific query. Each context also has a DNS cache which obeys the TTL (configuration exists to impose min/max if needed). The library also provides a single file descriptor on each context which can be polled on in a long lived thread. When data is available to be processed it becomes readable. The library provides individual functions for both sync and async resolution. The sync function will block the calling thread until results are available. The async function returns immediately and invokes a callback that is passed in with the results and a void pointer you pass in when resolution completes. The library also supports DNSSEC. This does require additional configuration but the functionality exists.
The library is available in more recent (well, semi-recent) distros (Ubuntu 10.04+, CentOS 5+).
Releases are made sporadically. Some years have more, some years have less. As the project consists of both a DNS server and a DNS client library aspect the mailing list is more active than that of c-ares. It sees between 20-30 emails on average per month. This also means that any contributions and improvements to the DNS server may also improve the DNS client. There is a publicly accessible issue tracker where contributions can be submitted and issues can be reported. Reported issues are also fixed quickly. Contributions are merged swiftly upon acceptance.
Core DNS API
Since DNS clients all share the same common fundamental interface it is easy to abstract them behind a core pluggable DNS API. This allows a different one to be plugged in if requirements or features change but also allows the presence of a third-party DNS client to be optional and not mandatory. If a third-party DNS client is not available a system level resolution module can be used instead. A numerical priority value will determine which module is used if multiple are present.