مقداردهی اولیه Async lazy برای اشیاءمنقضی شده
جمعه 26 آذر 1395، به عنوان بخشی از Net Framework. ما <Lazy<T را داریم ، که حمایتی را برای به تعویق انداختن ایجاد منابع بزرگ و یا منابع فشرده ، فراهم میآورد . حال اگر شی برای ایجاد شدن نیاز به عملیات async داشت ، و اگر مقدار آن بعد از یک مدت منقضی شد ، دوباره نیاز به یکسری انجام محاسبات دارد . که این یک مشکل است ، در این مقاله ما به ارائه راه حلی برای حل این مشکل خواهیم پرداخت .
یک نمونه مورد استفاده ساده :
اجازه دهید سناریوی زیر را فرض کنیم ،
شما در حال ایجاد یک سرویس تماس به یک HTTP API با استفاده از یک typical OAuth 2.0 client credentials flow می باشید . این بدان معناست که شما برای داشتن امکان استفاده از این برای برقراری تماس با Resource Server نیاز به فراهم آوردن یک Token از Identity Serrver دارید . بطور معمول بازیابی Token فقط یکبار مورد نیاز است ، و عملیات بازیابی token بهتر است که بصورت async باشد . در مجموع ، بر اساس OAuth spec ، جریان اعتبار کاربر از یک token تازه پشتیبانی نخواهد کرد ، پس زمانی که access token منقضی شود ، شما نیاز دارید که برای گرفتن یک Token جدید دوباره درخواست دهید .
تمام اینها بدین معناست که، ما با یک مقداردهی اولیه lazy سر و کار داریم (فقط یکبار token را میگیریم و در همان ابتدا با آن سر و کار داریم ) ، با کمک یک عملیات async و با یک object ای که منقضی خواهد شد .(پردازش و کار با token مدت زمان مشخصی دارد )
حال اجازه دهید به سراغ فراخوانی <AsyncExpiringLazy<T و پشتیبانی این سناریو در یک مسیر generic برویم .
ساخت یک <AsyncExpiringLazy<T :
در مجموع ، ما نیاز به یک Wrapper ساده برای محصور کردن نتیجه شی و زمان انقضای آن داریم . حال اجازه دهید به سراغ کد برویم :
public struct ExpirationMetadata<T> { public T Result { get; set; } public DateTimeOffset ValidUntil { get; set; } }
خوشبختانه ، این قسمت نیازی به توضیح آنچنانی ندارد . در مرحله بعد اجازه دهید به سراغ اضافه کردن AsyncExpiringLazy برویم . از زمانی که Property ها در #C نمیتوانند async باشند ، در AsyncExpiringLazy ما نیاز به استفاده از یک instance method تحت عنوان یک راه برای واکشی مقادیر مورد نیاز خواهیم داشت .
ابتدا اجازه دهید که کلاس های بیرونی را ایجاد کرده و سپس در ادامه کار بدنه کلاس ها را پر خواهیم کرد :
public class AsyncExpiringLazy<T> { public AsyncExpiringLazy(Func<ExpirationMetadata<T>, Task<ExpirationMetadata<T>>> valueProvider) { // TODO } public Task<bool> IsValueCreated() { // TODO } public async Task<T> Value() { // TODO } public Task Invalidate() { // TODO } }
پس Constructor یک مقدار Provider دریافت خواهد کرد - خود Provider مقادیر قبلی/قدیمی یا حتی مقادیر منقضی شده (یا در حال انقضا) را دریافت میکند و مسئولیت فراهم آوردن یک مقدار جدید بر عهده او میباشد . محصور کردن آن در ExpirationMetadata زمان انقضای آن را تعریف خواهد کرد .
در کدهای ما فقط "trick" برا یlocking از SemaphoreSlim استفاده خواهد کرد -- از زمانی که #C امکان استفاده از جمله قدیمی lock را برای شامل شدن await ندارد .
ما فقط می توانیم برای تنظیم مجدد ExpirationMetadata را به مقدار پیش فرض آن بازگردانیم .
کد در زیر نشان داده شده است :
public class AsyncExpiringLazy<T> { private static readonly SemaphoreSlim SyncLock = new SemaphoreSlim(initialCount: 1); private readonly Func<ExpirationMetadata<T>, Task<ExpirationMetadata<T>>> _valueProvider; private ExpirationMetadata<T> _value; public AsyncExpiringLazy(Func<ExpirationMetadata<T>, Task<ExpirationMetadata<T>>> valueProvider) { if (valueProvider == null) throw new ArgumentNullException(nameof(valueProvider)); _valueProvider = valueProvider; } private bool IsValueCreatedInternal => _value.Result != null && _value.ValidUntil > DateTimeOffset.UtcNow; public async Task<bool> IsValueCreated() { await _syncLock.WaitAsync().ConfigureAwait(false); try { return IsValueCreatedInternal; } finally { _syncLock.Release(); } } public async Task<T> Value() { await _syncLock.WaitAsync().ConfigureAwait(false); try { if (IsValueCreatedInternal) { return _value.Result; } } finally { _syncLock.Release(); } await _syncLock.WaitAsync().ConfigureAwait(false); try { var result = await _valueProvider(_value).ConfigureAwait(false); _value = result; return _value.Result; } finally { _syncLock.Release(); } } public async Task Invalidate() { await _syncLock.WaitAsync().ConfigureAwait(false); _value = default(ExpirationMetadata<T>); _syncLock.Release(); } }
روش استفاده :
استفاده از آن تقریبا ساده میباشد و در unit test بعدی نشان داده شده است :
// helper class for illustration public class TokenResponse { public string AccessToken { get; set; } } [Fact] public async Task End2End() { var testInstance = new AsyncExpiringLazy<TokenResponse>(async metadata => { await Task.Delay(1000); return new ExpirationMetadata<TokenResponse> { Result = new TokenResponse { AccessToken = Guid.NewGuid().ToString() }, ValidUntil = DateTimeOffset.UtcNow.AddSeconds(2) }; }); // 1. check if value is created - shouldn't Assert.False(await testInstance.IsValueCreated()); // 2. fetch lazy expiring value var token = await testInstance.Value(); // 3a. verify it is created now Assert.True(await testInstance.IsValueCreated()); // 3b. verify it is not null Assert.NotNull(token.AccessToken); // 4. fetch the value again. Since it's lifetime is 2 seconds, it should be still the same var token2 = await testInstance.Value(); Assert.Same(token, token2); // 5. sleep for 2 seconds to let the value expire await Task.Delay(2000); // 6. fetch again var token3 = await testInstance.Value(); // 7. verify we now have a new (recreated) value - as the previous one expired Assert.NotSame(token2, token3); // 8. invalidate the value manually before it has a chance to expire await testInstance.Invalidate(); // 9. check if value is created - shouldn't anymore Assert.False(await testInstance.IsValueCreated()); }
شما میتوانید یک نمونه از AsyncExpiringLazy را در هر نقطه ای ایجاد کنید -- برای مثال این میتواند یک فیلد در یکی از کلاس های شما باشد . شما نیاز به ارسال یک delegate که مسئول مقدار ایجاد شده است را دارید -- همچنین این delegate این امکان را به شما می دهد که مقادیر قدیمی را در صورت نیاز بازرسی کنید .
در مجموع ، همه ی اینها در برای انجام پردازش برای مقادیر در صورت نیاز است . و اگر این منقضی شده بود ، AsyncExpiringLazy در پردازش بعدی این را برای شما فراهم خواهد آورد .
آموزش asp.net mvc
- ASP.net MVC
- 1k بازدید
- 1 تشکر