ServiceStack.Redis missing Async Support - c#

I connect to redis in a cluster with the following code. I would use it in a webapi project with a lot of traffic and I'm concerned about the missing async support.
Does anyone have some experiences with this ? I also dindn't find an offical support email adress for this.
var sentinel = new RedisSentinel(sentinels, "mymaster");
var redisManager = sentinel.Start();
while (true)
{
Console.WriteLine("Key add: ");
string key = Console.ReadLine();
using (var redis = redisManager.GetClient())
{
string val = Guid.NewGuid().ToString();
redis.AddItemToList(key, val);
Console.WriteLine(val);
}
Console.WriteLine("done");
}

There is no pure async as you expect, but you can use workaround below. Its good enough for big load. You can run test from example below and test how it works for you and make decision to use it in your project or not.
public class ad_hook
{
[Fact]
public async Task test_redis_load()
{
var repository = GetRedis();
int expected;
IEnumerable<Task<string>> tasks;
using (var redisRepository = repository)
{
redisRepository.Set("foo", "boo");
expected = 10000;
tasks = Enumerable.Range(1, expected).Select(_ => Task.Run(() => redisRepository.Get<string>("foo")));
var items = await Task.WhenAll(tasks);
items.Should().OnlyContain(s => s == "boo")
.And.HaveCount(expected);
}
}
private static RedisRepository GetRedis()
{
var repository = new RedisRepository();
return repository;
}
}
public class RedisRepository : IDisposable
{
private Lazy<IRedisClient> clientFactory;
private PooledRedisClientManager clientManager;
private T Run<T>(Func<IRedisClient, T> action)
{
using (var client = GetRedisClient())
{
return action(client);
}
}
private IRedisClient GetRedisClient()
{
return clientManager.GetClient();
}
public RedisRepository()
{
clientFactory = new Lazy<IRedisClient>(GetRedisClient);
clientManager = new PooledRedisClientManager();
}
public void Set<T>(string key, T entity)
{
Run(_ => _.Set(key, entity));
}
public T Get<T>(string key)
{
return Run(_ => _.Get<T>(key));
}
public void Dispose()
{
clientManager.Dispose();
if (clientFactory.IsValueCreated)
{
clientFactory.Value.Dispose();
}
}
}

An update, some 5 years later:
ServiceStack has added Async support to its Redis libraries, from v 5.10 it seems (read more here):
This release continues to bring improvements across the board with a
major async focus on most of ServiceStack’s existing sync APIs gaining
pure async implementations allowing your App’s logic to use their
preferred sync or async APIs.
Example:

Related

WebApplicationFactory is running the Api with the old IConnectionMultiplexer which fails to connect instead of first overriding configuration

I'm working on an integration test for a Web API which communicates through Redis, so I tried to replace the Redis Server with a containerized one and run some tests.
The issue is that it is first running the Api with project's appsettings.Development.json configuration and the old IConnectionMultiplexer instance which obviously won't connect because the hostname is offline. The question is how do I make it run the project with the new IConnectionMultiplexer that uses the containerized Redis Server? Basically the sequence is wrong there. What I did is more like run the old IConnectionMultiplexer and replace it with the new one but it wouldn't connect to the old one, so that exception prevents me from continuing. I commented the line of code where it throws the exception but as I said it's obvious because it's first running the Api with the old configuration instead of first overriding the configuration and then running the Api.
I could have done something like the following but I'm DI'ing other services based on configuration as well, meaning I must override the configuration first and then run the actual API code.
try
{
var redis = ConnectionMultiplexer.Connect(redisConfig.Host);
serviceCollection.AddSingleton<IConnectionMultiplexer>(redis);
}
catch
{
// We discard that service if it's unable to connect
}
Api
public static class RedisConnectionConfiguration
{
public static void AddRedisConnection(this IServiceCollection serviceCollection, IConfiguration config)
{
var redisConfig = config.GetSection("Redis").Get<RedisConfiguration>();
serviceCollection.AddHostedService<RedisSubscription>();
serviceCollection.AddSingleton(redisConfig);
var redis = ConnectionMultiplexer.Connect(redisConfig.Host); // This fails because it didn't override Redis:Host
serviceCollection.AddSingleton<IConnectionMultiplexer>(redis);
}
}
Integration tests
public class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime
{
private const string Password = "Test1234!";
private readonly TestcontainersContainer _redisContainer;
private readonly int _externalPort = Random.Shared.Next(10_000, 60_000);
public OrderManagerApiFactory()
{
_redisContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("redis:alpine")
.WithEnvironment("REDIS_PASSWORD", Password)
.WithPortBinding(_externalPort, 6379)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
.Build();
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development");
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
});
builder.ConfigureAppConfiguration(config =>
{
config.AddInMemoryCollection(new Dictionary<string, string>
{
{ "Redis:Host", $"localhost:{_externalPort},password={Password},allowAdmin=true" },
{ "Redis:Channels:Main", "main:new:order" },
});
});
builder.ConfigureTestServices(services =>
{
services.RemoveAll(typeof(IConnectionMultiplexer));
services.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect($"localhost:{_externalPort},password={Password},allowAdmin=true"));
});
}
public async Task InitializeAsync()
{
await _redisContainer.StartAsync();
}
public new async Task DisposeAsync()
{
await _redisContainer.DisposeAsync();
}
}
public class OrderManagerTests : IClassFixture<OrderManagerApiFactory>, IAsyncLifetime
{
private readonly OrderManagerApiFactory _apiFactory;
public OrderManagerTests(OrderManagerApiFactory apiFactory)
{
_apiFactory = apiFactory;
}
[Fact]
public async Task Test()
{
// Arrange
var configuration = _apiFactory.Services.GetRequiredService<IConfiguration>();
var redis = _apiFactory.Services.GetRequiredService<IConnectionMultiplexer>();
var channel = configuration.GetValue<string>("Redis:Channels:Main");
// Act
await redis.GetSubscriber().PublishAsync(channel, "ping");
// Assert
}
public Task InitializeAsync()
{
return Task.CompletedTask;
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
Problem solved.
If you override WebApplicationFactory<T>.CreateHost() and call IHostBuilder.ConfigureHostConfiguration() before calling base.CreateHost() the configuration you add will be visible between WebApplication.CreateBuilder() and builder.Build().
The following two links might help someone:
https://github.com/dotnet/aspnetcore/issues/37680
https://github.com/dotnet/aspnetcore/issues/9275
public sealed class OrderManagerApiFactory : WebApplicationFactory<IApiMarker>, IAsyncLifetime
{
private const string Password = "Test1234!";
private const int ExternalPort = 7777; // Random.Shared.Next(10_000, 60_000);
private readonly TestcontainersContainer _redisContainer;
public OrderManagerApiFactory()
{
_redisContainer = new TestcontainersBuilder<TestcontainersContainer>()
.WithImage("redis:alpine")
.WithEnvironment("REDIS_PASSWORD", Password)
.WithPortBinding(ExternalPort, 6379)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(6379))
.Build();
}
public async Task InitializeAsync()
{
await _redisContainer.StartAsync();
}
public new async Task DisposeAsync()
{
await _redisContainer.DisposeAsync();
}
protected override IHost CreateHost(IHostBuilder builder)
{
builder.ConfigureHostConfiguration(config =>
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Redis:Host", $"localhost:{ExternalPort},password={Password},allowAdmin=true"),
new KeyValuePair<string, string>("Redis:Channels:Main", "main:new:order")
}));
return base.CreateHost(builder);
}
}

Integration between SignalR and WebAPI

I have a WebAPI server with integrated SignalR Hubs.
The problem it is in the integration between both components and efficiently call the clients interested on a given item that was updated through REST with the least overhead possible on the Controller side.
I have read about Background tasks with hosted services in ASP.NET Core, or Publish Subscriber Patterns but they don't seem the right fit for this problem.
From the documentation examples, the background tasks seem to atempt to preserve order which is not required, in fact, it is desired to allow multiple requests to be handled concurrently, as efficiently as possible.
With this in mind, I created this third component called MappingComponent that is being called through a new Task.
It is important to design the Controller in a way that he spends the least amount of work "raising the events" possible. Exceptions should be (i believe) handled within the MappingComponent.
What would be a better approach/design pattern that the following implementation, to avoid using Task.Run?
ApiController
[Route("api/[controller]")]
[ApiController]
public class ItemController : ControllerBase
{
private readonly MappingComponent mappingComponent;
private readonly IDataContext dataContext;
[HttpPost]
public async Task<ActionResult<Item>> PostItem(ItemDTO itemDTO)
{
await dataContext.Items.AddAsync(item);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemAdd(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return CreatedAtAction("Get", new { id = item.Id }, item);
}
[HttpDelete("{id}", Name = "Delete")]
public async Task<IActionResult> Delete(int id)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemDelete(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return NoContent();
}
[HttpPatch("{id}", Name = "Patch")]
public async Task<IActionResult> Patch(int id,
[FromBody] JsonPatchDocument<Item> itemToPatch)
{
var item = await dataContext.Items.FindAsync(id);
(...)
_ = Task.Run(async () =>
{
try
{
await mappingComponent.NotifyOnItemEdit(item);
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return StatusCode(StatusCodes.Status202Accepted);
}
}
SignalR Hub
public class BroadcastHub : Hub<IHubClient>
{
private readonly MappingComponent mappingComponent;
public BroadcastHub(MappingComponent mappingComponent)
{
this.mappingComponent = mappingComponent;
}
public override Task OnConnectedAsync()
{
mappingComponent.OnConnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync()
{
mappingComponent.OnDisconnected(Context.User.Identity.Name, Context.ConnectionId));
return base.OnDisconnectedAsync();
}
public void Subscribe(string itemQuery)
{
mappingComponent.SubscribeConnection(Context.User.Identity.Name, Context.ConnectionId, itemQuery));
}
public void Unsubscribe()
{
mappingComponent.UnsubscribeConnection(Context.ConnectionId));
}
}
"MappingComponent" being registered as singleton on startup
public class MappingComponent
{
private readonly IServiceScopeFactory scopeFactory;
private readonly IHubContext<BroadcastHub, IHubClient> _hubContext;
private static readonly ConcurrentDictionary<string, User> Users = new(StringComparer.InvariantCultureIgnoreCase);
private static readonly ConcurrentDictionary<int, List<string>> ItemConnection = new();
private static readonly ConcurrentDictionary<string, List<int>> ConnectionItem = new();
public MappingComponent(IServiceScopeFactory scopeFactory, IHubContext<BroadcastHub, IHubClient> hubContext)
{
//this.dataContext = dataContext;
this._hubContext = hubContext;
this.scopeFactory = scopeFactory;
}
internal void OnConnected(string userName, string connectionId){(...)}
internal void OnDisconnected(string userName, string connectionId){(...)}
internal void SubscribeConnection(string userName, string connectionId, string query){(...)}
internal void UnsubscribeConnection(string connectionId){(...)}
internal async Task NotifyOnItemAdd(Item item)
{
List<string> interestedConnections = new();
(...)
//Example containing locks
lock()
{
//There is a need to acess EF database
using (var scope = scopeFactory.CreateScope())
{
var dataContext = scope.ServiceProvider.GetRequiredService<IDataContext>()
await dataContext.Items.(...)
interestedConnections = ...
}
}
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemEdit(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastItem(item);
}
internal async Task NotifyOnItemDelete(Item item)
{
List<string> interestedConnections = new();
(...)
await _hubContext.Clients.Clients(interestedConnections).BroadcastAllItems();
}
}

How to get status of long running task

Why does the Status of my Task return "WaitingForActivasion" instead of "Running" ?
If I remove Task.Run I get stuck in the while loop, so I assume its not running asynchronous.
public class StateManagerTest
{
[Fact]
public void Start_TaskStatus()
{
StateManager manager = new StateManager();
manager.Start();
Assert.True(manager.Status == System.Threading.Tasks.TaskStatus.Running.ToString());
}
}
public class StateManager
{
private CancellationTokenSource cts = new();
private Task updateTask;
public HashSet<StateItem> StateItems { get; private set; }
public Provider Provider { get; private set; }
public List<OutputService> OutputServices { get; private set; }
public string Status
{
get => updateTask.Status.ToString();
}
public StateManager()
{
StateItems = new();
OutputServices = new();
Provider = new();
}
public void Stop()
{
cts.Cancel();
}
public void Start()
{
updateTask = Task.Run(() => Update(cts.Token))
.ContinueWith(t => Debug.WriteLine(t.Exception.Message), TaskContinuationOptions.OnlyOnFaulted);
}
private async Task Update(CancellationToken token)
{
while (true)
{
// get changes from outputs
Dictionary<StateItem, object> changes = new Dictionary<StateItem, object>();
foreach (var service in OutputServices)
{
var outputChanges = await service.GetChanges();
foreach (var change in outputChanges)
changes.TryAdd(change.Key, change.Value);
}
// write changes to provider source
await Provider.PushChanges(changes);
// update state
await Provider.UpdateStateItems();
// update all services
foreach (var service in OutputServices)
await service.UpdateSource();
if (token.IsCancellationRequested)
return;
}
}
}
As others have noted, WaitingForActivation is the correct state for a Promise Task that is not yet completed. In general, I recommend not using Task.Status or ContinueWith; they are relics from a time before async/await existed.
How to get status of long running task
I believe you would want progress reporting, which is done yourself. The T in IProgress<T> can be a string if you want a simple text update, or a double if you want a percentage update, or a custom struct if you want a more complex update.

Simple.OData.Client really slow

I'm using Simple.OData.Client to query and update in our crm dynamics system.
But each query, insertion or update takes up to 10 seconds. It works like a charm on postman. That means that the server is not the problem.
Here is my Code:
Base Class
public abstract class CrmBaseDao<T> where T : class
{
protected ODataClient GetClient()
{
return new ODataClient(new ODataClientSettings(BuildServiceUrl())
{
Credentials = new NetworkCredential(Settings.Default.CrmUsername, Settings.Default.CrmPassword),
IgnoreUnmappedProperties = true
});
}
public async Task<IEnumerable<T>> GetAll()
{
var client = GetClient();
return await client.For<T>().FindEntriesAsync();
}
private string BuildServiceUrl()
{
return Settings.Default.CrmBaseUrl + "/api/data/v8.2/";
}
}
Derived class:
public void Insert(Account entity)
{
var task = GetClient()
.For<Account>()
.Set(ConvertToAnonymousType(entity))
.InsertEntryAsync();
task.Wait();
entity.accountid = task.Result.accountid;
}
public void Update(Account entity)
{
var task = GetClient()
.For<Account>()
.Key(entity.accountid)
.Set(ConvertToAnonymousType(entity))
.UpdateEntryAsync();
task.Wait();
}
private object ConvertToAnonymousType(Account entity)
{
return new
{
entity.address1_city,
entity.address1_fax,
entity.address1_line1,
entity.address1_postalcode,
entity.address1_stateorprovince,
entity.he_accountnumber,
entity.name,
entity.telephone1,
entity.telephone2
};
}
public async Task<Account> GetByHeAccountNumber(string accountNumber)
{
return await GetClient().For<Account>()
.Filter(x => x.he_accountnumber == accountNumber)
.FindEntryAsync();
}
The call:
private void InsertIDocsToCrm()
{
foreach (var file in GetAllXmlFiles(Settings.Default.IDocPath))
{
var sapAccountEntity = GetSapAccountEntity(file);
var crmAccountEntity = AccountConverter.Convert(sapAccountEntity);
var existingAccount = crmAccountDao.GetByHeAccountNumber(crmAccountEntity.he_accountnumber);
existingAccount.Wait();
if (existingAccount.Result != null)
{
crmAccountEntity.accountid = existingAccount.Result.accountid;
crmAccountDao.Update(crmAccountEntity);
}
else
crmAccountDao.Insert(crmAccountEntity);
}
}
This whole function takes a very long time (30 sec+)
Is there any chance to speed that up?
Additionaly it does take a lot of memory?!
Thanks
I suspect this might have to do with a size of schema. I will follow the issue you opened on GitHub.
UPDATE. I ran some benchmarks mocking server responses, and processing inside Simple.OData.Client took just milliseconds. I suggest you run benchmarks on a server side. You can also try assigning metadata file references to MetadataDocument property of ODataClientSettings.

unable to use Async await in calling soap service

I am trying to implement a async await in my web application for calling a soap service. I have dependency injection implemented which works fine when i make calls to the database.
When i try calling the webservice i get the response but once it exists the query it's a deadlock. I am not able to figure out what's going wrong. I am new to this async stuff appreciate your inputs on this.
This is the way i am calling the services in a mvc application to invoke the call
public void GetPersonData()
{
var persons = queryProcessor.Process(new GetPersonsWhichNeedApiCalls());
var watch = System.Diagnostics.Stopwatch.StartNew();
// the code that you want to measure comes here
SearchAPI(persons).Wait();
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
}
private async Task SearchAPI(IEnumerable<Person> persons)
{
var tasks = persons.Select(async eachPerson =>
{
var result = await asyncQueryProcessor.ProcessAsync(new PersonSearchCall { Name = eachPerson.Name, Id = eachPerson.Id });
commandProcessor.Process(new CreatePersonSearch()
{
PersonSearch = result
});
});
await Task.WhenAll(tasks);
}
query:
namespace Sample.AsyncQueries
{
public class PersonSearchCall : IQuery<PersonSearch>
{
public string Name { get; set; }
public int Id { get; set; }
}
public class PersonSearchCallHandler : IAsyncQueryHandler<PersonSearchCall, PersonSearch>
{
private readonly IQueryProcessor queryProcessor;
private readonly ICommandProcessor commandProcessor;
public PersonSearchCallHandler(ICommandProcessor commandProcessor,
IQueryProcessor queryProcessor)
{
this.queryProcessor = queryProcessor;
this.commandProcessor = commandProcessor;
}
public async Task<PersonSearch> HandleAsync(PersonSearchCall query)
{
var client = new PersonServiceSoapClient();
var personResponses = await client.PersonSearchAsync(inputs).ConfigureAwait(false);
//Build the person Object
return person;
}
}
}
This simple injector i was able to achieve this using the synchronous way but as i am calling a list of persons and each call is taking around 2sec. i am trying to leverage the use of async and await to make multiple calls from the list.
As StriplingWarrior commented, your problem is that you're blocking on async code. You need to use async all the way:
public async Task GetPersonData()
{
var persons= queryProcessor.Process(new GetPersonsWhichNeedApiCalls());
var watch = System.Diagnostics.Stopwatch.StartNew();
await SearchAPI(persons);
var elapsedMs = watch.ElapsedMilliseconds;
}

Categories