diff --git a/Bootstrap.Admin/Bootstrap.Admin.csproj b/Bootstrap.Admin/Bootstrap.Admin.csproj index e3036fc4..79ff93df 100644 --- a/Bootstrap.Admin/Bootstrap.Admin.csproj +++ b/Bootstrap.Admin/Bootstrap.Admin.csproj @@ -12,8 +12,9 @@ - - + + + diff --git a/Bootstrap.Admin/Controllers/AdminController.cs b/Bootstrap.Admin/Controllers/AdminController.cs index 44aa0bad..a8990649 100644 --- a/Bootstrap.Admin/Controllers/AdminController.cs +++ b/Bootstrap.Admin/Controllers/AdminController.cs @@ -114,7 +114,7 @@ namespace Bootstrap.Admin.Controllers /// /// /// - public ActionResult Tasks() => View(new NavigatorBarModel(this)); + public ActionResult Tasks() => View(new TaskModel(this)); /// /// diff --git a/Bootstrap.Admin/Controllers/Api/TasksController.cs b/Bootstrap.Admin/Controllers/Api/TasksController.cs index ef5296d6..29bc80be 100644 --- a/Bootstrap.Admin/Controllers/Api/TasksController.cs +++ b/Bootstrap.Admin/Controllers/Api/TasksController.cs @@ -1,24 +1,66 @@ -using Bootstrap.DataAccess; +using Longbow.Tasks; +using Longbow.Web.SignalR; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace Bootstrap.Admin.Controllers.Api { /// - /// + /// /// [Route("api/[controller]")] [ApiController] public class TasksController : ControllerBase { /// - /// + /// /// /// [HttpGet] - public IEnumerable Get() + public IEnumerable Get() { - return TaskHelper.Retrieves(); + TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), TriggerBuilder.WithCronExpression("*/5 * * * * *")); + return TaskServicesManager.ToList().Select(s => new { s.Name, s.Enabled, s.Status, s.LastRuntime, s.CreatedTime, s.NextRuntime, Triggers = s.Triggers.Count, s.LastRunResult, TriggerExpression = s.Triggers.FirstOrDefault().ToString() }); } + + /// + /// + /// + /// + [HttpPost] + public bool Post() + { + // UNDONE: 待完善 + return true; + } + + /// + /// + /// + /// + /// + /// + [HttpPut] + public bool Put([FromQuery]string name, [FromServices]IHubContext hub) + { + var sche = TaskServicesManager.GetOrAdd(name); + sche.Triggers[0].RegisterPulseCallback(async t => + { + var success = t.Cancelled ? "Cancelled" : "Success"; + var result = $"{t.Scheduler.LastRuntime.Value.DateTime}: Trigger({t.GetType().Name}) Run({success}) NextRuntime: {t.NextRuntime.Value.DateTime} Elapsed: {t.LastRunElapsedTime.Seconds}s"; + await SignalRManager.SendTaskLog(hub.Clients.All, result); + }); + return true; + } + + /// + /// + /// + /// + [HttpDelete] + public bool Delete() => true; } -} \ No newline at end of file +} diff --git a/Bootstrap.Admin/Models/TaskModel.cs b/Bootstrap.Admin/Models/TaskModel.cs new file mode 100644 index 00000000..e821bdab --- /dev/null +++ b/Bootstrap.Admin/Models/TaskModel.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace Bootstrap.Admin.Models +{ + /// + /// + /// + public class TaskModel : NavigatorBarModel + { + /// + /// + /// + /// + public TaskModel(ControllerBase controller) : base(controller) + { + Tasks = new string[] { "测试任务" }; + } + + /// + /// 获得 系统配置的所有任务 + /// + public IEnumerable Tasks { get; } + } +} diff --git a/Bootstrap.Admin/SignalR/SignalRManager.cs b/Bootstrap.Admin/SignalR/SignalRManager.cs index 10319e24..050c98af 100644 --- a/Bootstrap.Admin/SignalR/SignalRManager.cs +++ b/Bootstrap.Admin/SignalR/SignalRManager.cs @@ -16,7 +16,16 @@ namespace Bootstrap.Admin /// /// /// - public static async System.Threading.Tasks.Task Send(IClientProxy client, MessageBody args) => await client.SendAsync("rev", args); + public static System.Threading.Tasks.Task Send(IClientProxy client, MessageBody args) => client.SendAsync("rev", args); + + /// + /// + /// + /// + /// + /// + public static System.Threading.Tasks.Task SendTaskLog(IClientProxy client, string args) => client.SendAsync("taskRev", args); + /// /// /// diff --git a/Bootstrap.Admin/Startup.cs b/Bootstrap.Admin/Startup.cs index 18957079..794c7344 100644 --- a/Bootstrap.Admin/Startup.cs +++ b/Bootstrap.Admin/Startup.cs @@ -63,6 +63,7 @@ namespace Bootstrap.Admin services.AddBootstrapAdminAuthentication(); services.AddSwagger(); services.AddButtonAuthorization(MenuHelper.AuthorizateButtons); + services.AddTaskServices(); services.AddMvc(options => { options.Filters.Add(); diff --git a/Bootstrap.Admin/Views/Admin/Dicts.cshtml b/Bootstrap.Admin/Views/Admin/Dicts.cshtml index a2b0ef7f..04c7377c 100644 --- a/Bootstrap.Admin/Views/Admin/Dicts.cshtml +++ b/Bootstrap.Admin/Views/Admin/Dicts.cshtml @@ -41,7 +41,7 @@ } -@section body { +@section cardbody { diff --git a/Bootstrap.Admin/Views/Admin/Menus.cshtml b/Bootstrap.Admin/Views/Admin/Menus.cshtml index 1395ec3a..04b3c0df 100644 --- a/Bootstrap.Admin/Views/Admin/Menus.cshtml +++ b/Bootstrap.Admin/Views/Admin/Menus.cshtml @@ -90,7 +90,7 @@ @section gear { } -@section body { +@section cardbody { diff --git a/Bootstrap.Admin/Views/Admin/Roles.cshtml b/Bootstrap.Admin/Views/Admin/Roles.cshtml index 9f06d9e8..6a9244a6 100644 --- a/Bootstrap.Admin/Views/Admin/Roles.cshtml +++ b/Bootstrap.Admin/Views/Admin/Roles.cshtml @@ -58,7 +58,7 @@ } -@section body { +@section cardbody { diff --git a/Bootstrap.Admin/Views/Admin/Tasks.cshtml b/Bootstrap.Admin/Views/Admin/Tasks.cshtml index 69fa01a1..87e76718 100644 --- a/Bootstrap.Admin/Views/Admin/Tasks.cshtml +++ b/Bootstrap.Admin/Views/Admin/Tasks.cshtml @@ -1,18 +1,99 @@ -@model NavigatorBarModel +@model TaskModel @{ ViewBag.Title = "任务管理"; + Layout = "_Default"; } @section css { + } @section javascript { + } -
-
任务消息
-
-
-
    +@section tableButtons { + +} +@section cardbody { + +} +@section query { +
    +
    +

    后台任务说明:

    +

    1. 默认任务 (立即执行,仅执行一次)

    +

    + + TaskServicesManager.GetOrAdd("简单任务", token => Task.Delay(1000)); + +

    +

    2. 周期性任务 (1 分钟后间隔 5 秒执行2次任务)

    +

    + + var trigger = TriggerBuilder.Default.WithInterval(TimeSpan.FromSeconds(5)).WithRepeatCount(2).WithStartTime(DateTimeOffset.Now.AddMinutes(1)).Build(); + TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), trigger); + +

    +

    3. Cron表达式任务 (间隔 5 秒循环执行任务)

    +

    + + TaskServicesManager.GetOrAdd("Cron表达式任务", token => Task.Delay(1000), TriggerBuilder.WithCronExpression("*/5 * * * * *")); + +

    -
    \ No newline at end of file +} +@section modal { + + +} +@section customModal { + +} \ No newline at end of file diff --git a/Bootstrap.Admin/Views/Admin/Users.cshtml b/Bootstrap.Admin/Views/Admin/Users.cshtml index b4f507f6..83fbac08 100644 --- a/Bootstrap.Admin/Views/Admin/Users.cshtml +++ b/Bootstrap.Admin/Views/Admin/Users.cshtml @@ -38,7 +38,7 @@ @section tableButtons { } -@section body { +@section cardbody { diff --git a/Bootstrap.Admin/Views/Shared/_Default.cshtml b/Bootstrap.Admin/Views/Shared/_Default.cshtml index 129fb0ca..50e46582 100644 --- a/Bootstrap.Admin/Views/Shared/_Default.cshtml +++ b/Bootstrap.Admin/Views/Shared/_Default.cshtml @@ -80,7 +80,7 @@ 查询结果
    - @await RenderSectionAsync("body", false) + @await RenderSectionAsync("cardbody", false)
    @@ -90,4 +90,5 @@ @RenderSection("tableButtons", false) - \ No newline at end of file + +@await RenderSectionAsync("body", false) \ No newline at end of file diff --git a/Bootstrap.Admin/wwwroot/css/theme.css b/Bootstrap.Admin/wwwroot/css/theme.css index 34653c98..cc2c7ee3 100644 --- a/Bootstrap.Admin/wwwroot/css/theme.css +++ b/Bootstrap.Admin/wwwroot/css/theme.css @@ -536,3 +536,11 @@ input.pending { .alert-danger { border-left: solid 4px #f78792; } + +.tip { + padding: 0.5rem 1rem; + background-color: #ecf8ff; + border-radius: 4px; + border-left: 5px solid #50bfff; + margin-bottom: 1rem; +} \ No newline at end of file diff --git a/Bootstrap.Admin/wwwroot/js/tasks.js b/Bootstrap.Admin/wwwroot/js/tasks.js index 9b512193..315ba288 100644 --- a/Bootstrap.Admin/wwwroot/js/tasks.js +++ b/Bootstrap.Admin/wwwroot/js/tasks.js @@ -1,21 +1,82 @@ $(function () { - var htmlTask = '
  • {0}{1}{2}
  • '; - - $('#refreshTask').on('click', function () { - var that = $(this); - that.toggleClass('fa-spin'); - $.bc({ - url: Tasks.url, - autoFooter: true, - callback: function (result) { - if (result) { - var content = result.map(function (task) { - return $.format(htmlTask, task.TaskName, task.UserName, task.AssignTime, task.Id); - }).join(''); - $('#list-task').html(content); - } - that.toggleClass('fa-spin'); + var $taskMsg = $('#taskMsg'); + var $taskLogModelTitle = $('#taskModalLabel'); + var stateFormatter = function (value) { + var template = ""; + var content = ""; + if (value === "0") { + content = $.format(template, 'info', 'fa', '未开始'); + } + else if (value === "1") { + content = $.format(template, 'success', 'play-circle', '运行中'); + } + else if (value === "2") { + content = $.format(template, 'primary', 'stop-circle', '已停止'); + } + else if (value === "3") { + content = $.format(template, 'danger', 'times-circle', '已禁用'); + } + return content; + }; + var enabledFormatter = function (value) { + var template = ""; + return $.format(template, value ? 'on' : 'off'); + }; + $('.card-body table').lgbTable({ + url: Tasks.url, + dataBinder: { + map: { + Id: "#taskID", + Name: "#taskName" } - }); - }).trigger('click'); + }, + smartTable: { + sortName: 'CreateTime', + sortOrder: 'desc', + queryParams: function (params) { return $.extend(params, { operateType: $("#txt_operate_type").val(), OperateTimeStart: $("#txt_operate_start").val(), OperateTimeEnd: $("#txt_operate_end").val() }); }, + columns: [ + { title: "名称", field: "Name", sortable: true }, + { title: "创建时间", field: "CreatedTime", sortable: true }, + { title: "上次执行时间", field: "LastRuntime", sortable: true }, + { title: "下次执行时间", field: "NextRuntime", sortable: true }, + { title: "触发条件", field: "TriggerExpression", sortable: false }, + { title: "是否启用", field: "Enabled", sortable: true, formatter: enabledFormatter }, + { title: "状态", field: "Status", sortable: true, align: 'center', width: 106, formatter: stateFormatter } + ], + editButtons: { + events: { + 'click .info': function (e, value, row, index) { + $taskLogModelTitle.html(row.Name + ' - 任务日志窗口(最新50条)'); + $.bc({ + url: 'api/Tasks?name=' + row.Name, + method: 'put' + }); + $('#dialogLog').modal('show').on('hide.bs.modal', function () { + // close hub + if ($taskMsg.hub) $taskMsg.hub.stop(); + $taskMsg.html(''); + }); + + var lastMsg = ""; + // open hub + $taskMsg.notifi({ + url: 'NotiHub', + method: 'taskRev', + callback: function (result) { + if (lastMsg === result) return; + lastMsg = result; + while (this.children().length > 50) { + this.children().first().remove(); + } + this.append('
    ' + result + '
    '); + }, + onclose: function (error) { + console.log(error); + } + }); + } + } + } + } + }); }); \ No newline at end of file diff --git a/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.css b/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.css new file mode 100644 index 00000000..bcc7b5b3 --- /dev/null +++ b/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.css @@ -0,0 +1,148 @@ +.form-select { + position: relative; +} + + .form-select .dropdown-menu { + margin-top: 14px; + max-height: 274px; + overflow-x: hidden; + padding: 0.25rem 0; + } + + .form-select .dropdown-menu-arrow { + width: 0; + height: 0; + border-width: 0 6px 6px; + border-style: solid; + border-color: transparent transparent rgba(0,0,0,.15); + position: absolute; + left: 20px; + z-index: 1001; + margin-top: 8px; + } + + .form-select .dropdown-menu, .form-select .dropdown-menu-arrow { + display: none; + opacity: 0; + transition: opacity .3s; + } + + .form-select .dropdown-menu-arrow:after { + content: " "; + width: 0; + height: 0; + border-width: 0 6px 6px; + border-style: solid; + border-color: transparent transparent #fff; + position: absolute; + top: 1px; + left: -6px; + } + + .form-select .form-select-input[readonly] { + background-color: #fff; + } + + .form-select.open .dropdown-menu, .form-select.open .dropdown-menu-arrow { + display: block; + opacity: 1; + } + + .form-select.open .form-select-append i { + transform: rotate(0); + } + + .form-select.is-disabled .form-select-input { + background-color: #f5f7fa; + border-color: #e4e7ed; + color: #c0c4cc; + cursor: not-allowed; + } + +.form-select-input { + color: #606266; + outline: none; + padding-right: 30px; + cursor: pointer; +} + + .form-select-input:hover { + border-color: #c0c4cc; + } + + .form-select-input.primary:hover { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6); + border-color: #66afe9; + } + + .form-select-input.info:hover { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5); + border-color: #17a2b8; + } + + .form-select-input.success:hover { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5); + border-color: #28a745; + } + + .form-select-input.warning:hover { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5); + border-color: #ffc107; + } + + .form-select-input.danger:hover { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5); + border: 1px solid #dc3545; + } + + .form-select-input:focus { + border-color: #409eff; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6); + } + + .form-select-input.primary:focus { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102,175,233,.6); + border-color: #66afe9; + } + + .form-select-input.info:focus { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(23, 162, 184, 0.5); + border-color: #17a2b8; + } + + .form-select-input.success:focus { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(40, 167, 69, 0.5); + border-color: #28a745; + } + + .form-select-input.warning:focus { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(255, 193, 7, 0.5); + border-color: #ffc107; + } + + .form-select-input.danger:focus { + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(220, 53, 69, 0.5); + border: 1px solid #dc3545; + } + + .form-select-input.is-valid, .form-select-input.is-invalid { + background-image: none; + } + +.form-select-append { + position: absolute; + height: 100%; + width: 30px; + right: 0; + top: 0; + color: #c0c4cc; + pointer-events: none; + display: flex; + align-items: center; + justify-content: center; +} + + .form-select-append i { + transition: all .3s; + transform: rotate(180deg); + } diff --git a/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.js b/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.js new file mode 100644 index 00000000..c3857629 --- /dev/null +++ b/Bootstrap.Admin/wwwroot/lib/longbow-select/longbow-select.js @@ -0,0 +1,166 @@ +(function ($) { + 'use strict'; + + var lgbSelect = function (element, options) { + this.$element = $(element); + this.options = $.extend({}, lgbSelect.DEFAULTS, options); + this.init(); + }; + + lgbSelect.VERSION = '1.0'; + lgbSelect.Author = 'argo@163.com'; + lgbSelect.DataKey = "lgb.select"; + lgbSelect.Template = '
    '; + lgbSelect.Template += ''; + lgbSelect.Template += ''; + lgbSelect.Template += ' '; + lgbSelect.Template += ''; + lgbSelect.Template += ''; + lgbSelect.Template += ''; + lgbSelect.Template += '
    '; + lgbSelect.DEFAULTS = { + placeholder: "请选择 ...", + borderClass: null + }; + lgbSelect.AllowMethods = /disabled|enable|val|reset/; + + function Plugin(option) { + var params = $.makeArray(arguments).slice(1); + return this.each(function () { + var $this = $(this); + var data = $this.data(lgbSelect.DataKey); + var options = typeof option === 'object' && option; + + if (!data) $this.data(lgbSelect.DataKey, data = new lgbSelect(this, options)); + if (!lgbSelect.AllowMethods.test(option)) return; + if (typeof option === 'string') { + data[option].apply(data, params); + } + }); + } + + $.fn.lgbSelect = Plugin; + $.fn.lgbSelect.Constructor = lgbSelect; + + var _proto = lgbSelect.prototype; + _proto.init = function () { + var getUID = function (prefix) { + if (!prefix) prefix = 'lgb'; + do prefix += ~~(Math.random() * 1000000); + while (document.getElementById(prefix)); + return prefix; + }; + + var that = this; + this.$element.addClass('d-none'); + this.$ctl = $(lgbSelect.Template).insertBefore(this.$element); + this.$input = this.$ctl.find('.form-select-input'); + this.$menus = this.$ctl.find('.dropdown-menu'); + + // init for + var $for = this.$element.parent().find('[for="' + this.$element.attr('id') + '"]'); + if ($for.length > 0) { + var id = getUID(); + this.$input.attr('id', id); + $for.attr('for', id); + } + + if (this.options.borderClass) { + this.$input.addClass(this.options.borderClass); + } + this.$input.attr('placeholder', this.options.placeholder); + + // init dropdown-menu + var data = this.$element.find('option').map(function () { + return { value: this.value, text: this.text, selected: this.selected } + }); + this.reset(data); + + // bind attribute + ["data-valid", "data-required-msg"].forEach(function (v, index) { + if (that.$element.attr(v) !== undefined) { + that.$input.attr(v, that.$element.attr(v)); + } + }); + + // replace element select -> input hidden + var eid = this.$element.attr('id'); + this.$element.remove(); + this.$element = $('').attr('id', eid).val(that.val()).insertAfter(this.$ctl); + this.$element.data(lgbSelect.DataKey, this); + + // bind event + this.$ctl.on('click', '.form-select-input', function (e) { + e.preventDefault(); + + that.$ctl.toggleClass('open'); + // calc width + that.$ctl.find('.dropdown-menu').outerWidth(that.$ctl.outerWidth()); + }); + + this.$ctl.on('click', 'a.dropdown-item', function (e) { + e.preventDefault(); + + var $this = $(this); + $this.parent().children().removeClass('active'); + that.val($this.attr('data-val'), true); + }); + + $(document).on('click', function (e) { + if (that.$input[0] !== e.target) + that.closeMenu(); + }); + }; + + _proto.closeMenu = function () { + this.$ctl.removeClass('open'); + }; + + _proto.disabled = function () { + this.$ctl.addClass('is-disabled'); + this.$input.attr('disabled', 'disabled'); + }; + + _proto.enable = function () { + this.$ctl.removeClass('is-disabled'); + this.$input.removeAttr('disabled'); + }; + + _proto.reset = function (value) { + var that = this; + // keep old value + var oldValue = this.$element.val(); + this.val(''); + this.$menus.html(''); + $.each(value, function () { + var $item = $('' + this.text + ''); + that.$menus.append($item); + if (this.selected === true || this.value.toString() === oldValue) { + that.val(this.value); + } + }); + }; + + _proto.val = function (value, valid) { + if (value !== undefined) { + var text = this.$menus.find('a.dropdown-item[data-val="' + value + '"]').text(); + this.$input.val(text); + this.$element.val(value).attr('data-text', text); + this.$menus.find('.dropdown-item').removeClass('active'); + this.$menus.find('.dropdown-item[data-val="' + value + '"]').addClass('active'); + + // trigger changed.lgbselect + this.$element.trigger('changed.lgbSelect'); + + // trigger lgbValidate + if (valid && this.$input.attr('data-valid') === 'true') this.$input.trigger('input.lgb.validate'); + } + else { + return this.$element.val(); + } + }; + + $(function () { + $('[data-toggle="lgbSelect"]').lgbSelect(); + }); +})(jQuery); diff --git a/Bootstrap.Admin/wwwroot/lib/longbow/longbow.common.js b/Bootstrap.Admin/wwwroot/lib/longbow/longbow.common.js index a5e5198a..461b44b0 100644 --- a/Bootstrap.Admin/wwwroot/lib/longbow/longbow.common.js +++ b/Bootstrap.Admin/wwwroot/lib/longbow/longbow.common.js @@ -444,8 +444,12 @@ if ($.isFunction(op.callback)) op.callback.apply(that, arguments); return console.error(err.toString()); }).then(function () { + // 连接成功 + // invoke 为 调用服务端方法 + // invoke: function (connection) { return connection.invoke('RetrieveDashboard'); } if (op.invoke) op.invoke(connection).then(function (result) { console.log(result); }).catch(function (err) { console.error(err.toString()); }); }); + this.hub = connection; return this; } }); diff --git a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj index 796ea5ae..4e1b388a 100644 --- a/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj +++ b/Bootstrap.Client.DataAccess/Bootstrap.Client.DataAccess.csproj @@ -5,7 +5,7 @@ - + diff --git a/Bootstrap.Client/Bootstrap.Client.csproj b/Bootstrap.Client/Bootstrap.Client.csproj index 5dde12bf..1cedbf7c 100644 --- a/Bootstrap.Client/Bootstrap.Client.csproj +++ b/Bootstrap.Client/Bootstrap.Client.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj index df3d3f0b..5a31b733 100644 --- a/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj +++ b/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj @@ -5,9 +5,9 @@ - + - + diff --git a/BootstrapAdmin.sln b/BootstrapAdmin.sln index 8df7a4ec..55a2aa63 100644 --- a/BootstrapAdmin.sln +++ b/BootstrapAdmin.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.28307.572 +VisualStudioVersion = 15.0.28307.705 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SQLServer", "SQLServer", "{87319AF5-7C40-4362-B67C-35F9DD737DB4}" ProjectSection(SolutionItems) = preProject diff --git a/UnitTest/TaskTest.cs b/UnitTest/TaskTest.cs new file mode 100644 index 00000000..77be0430 --- /dev/null +++ b/UnitTest/TaskTest.cs @@ -0,0 +1,47 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace UnitTest +{ + public class TaskTest + { + private ITestOutputHelper _helper; + + public TaskTest(ITestOutputHelper helper) => _helper = helper; + + [Fact] + public async void Task_Ok() + { + var cts = new CancellationTokenSource(3000); + var sw = Stopwatch.StartNew(); + try + { + // await Task.Delay(3000, cts.Token); + + await TestAsync(cts.Token); + } + catch (Exception) { } + finally + { + sw.Stop(); + } + _helper.WriteLine(sw.ElapsedMilliseconds.ToString()); + + //var waiter = new CancellationTokenSource(1000); + //var task = TestAsync(waiter.Token); + //task.Wait(waiter.Token); + ////try + ////{ + //// task.Wait(2000, waiter.Token); + ////} + ////catch (Exception) { } + //sw.Stop(); + } + + private Task TestAsync(CancellationToken token) => Task.Delay(2000, token); + } +}